github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/master/openapi_controller.go (about) 1 // Copyright 2022 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 // MVC for dm-master's openapi server 15 // Model(data in etcd): source of truth 16 // View(openapi_view): do some inner work such as validate, filter, prepare parameters/response and call controller to update model. 17 // Controller(openapi_controller): call model func to update data. 18 19 package master 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "strings" 26 27 "github.com/pingcap/log" 28 "github.com/pingcap/tiflow/dm/checker" 29 dmcommon "github.com/pingcap/tiflow/dm/common" 30 "github.com/pingcap/tiflow/dm/config" 31 "github.com/pingcap/tiflow/dm/ctl/common" 32 "github.com/pingcap/tiflow/dm/master/scheduler" 33 "github.com/pingcap/tiflow/dm/master/workerrpc" 34 "github.com/pingcap/tiflow/dm/openapi" 35 "github.com/pingcap/tiflow/dm/pb" 36 "github.com/pingcap/tiflow/dm/pkg/ha" 37 "github.com/pingcap/tiflow/dm/pkg/terror" 38 clientv3 "go.etcd.io/etcd/client/v3" 39 "go.uber.org/zap" 40 ) 41 42 // nolint:unparam 43 func (s *Server) getClusterInfo(ctx context.Context) (*openapi.GetClusterInfoResponse, error) { 44 info := &openapi.GetClusterInfoResponse{} 45 info.ClusterId = s.ClusterID() 46 47 resp, err := s.etcdClient.Get(ctx, dmcommon.ClusterTopologyKey) 48 if err != nil { 49 return nil, err 50 } 51 52 // already set by tiup, load to info 53 if len(resp.Kvs) == 1 { 54 topo := &openapi.ClusterTopology{} 55 if err := json.Unmarshal(resp.Kvs[0].Value, topo); err != nil { 56 return nil, err 57 } 58 info.Topology = topo 59 } 60 return info, nil 61 } 62 63 func (s *Server) updateClusterInfo(ctx context.Context, topo *openapi.ClusterTopology) (*openapi.GetClusterInfoResponse, error) { 64 if val, err := json.Marshal(topo); err != nil { 65 return nil, err 66 } else if _, err := s.etcdClient.Put(ctx, dmcommon.ClusterTopologyKey, string(val)); err != nil { 67 return nil, err 68 } 69 info := &openapi.GetClusterInfoResponse{} 70 info.ClusterId = s.ClusterID() 71 info.Topology = topo 72 return info, nil 73 } 74 75 func (s *Server) getSourceStatusListFromWorker(ctx context.Context, sourceName string, specifiedSource bool) ([]openapi.SourceStatus, error) { 76 workerStatusList := s.getStatusFromWorkers(ctx, []string{sourceName}, "", specifiedSource) 77 sourceStatusList := make([]openapi.SourceStatus, len(workerStatusList)) 78 for i, workerStatus := range workerStatusList { 79 if workerStatus == nil { 80 // this should not happen unless the rpc in the worker server has been modified 81 return nil, terror.ErrOpenAPICommonError.New("worker's query-status response is nil") 82 } 83 sourceStatus := openapi.SourceStatus{SourceName: sourceName, WorkerName: workerStatus.SourceStatus.Worker} 84 if !workerStatus.Result { 85 sourceStatus.ErrorMsg = &workerStatus.Msg 86 sourceStatusList[i] = sourceStatus 87 continue 88 } 89 90 if relayStatus := workerStatus.SourceStatus.GetRelayStatus(); relayStatus != nil { 91 sourceStatus.RelayStatus = &openapi.RelayStatus{ 92 MasterBinlog: relayStatus.MasterBinlog, 93 MasterBinlogGtid: relayStatus.MasterBinlogGtid, 94 RelayBinlogGtid: relayStatus.RelayBinlogGtid, 95 RelayCatchUpMaster: relayStatus.RelayCatchUpMaster, 96 RelayDir: relayStatus.RelaySubDir, 97 Stage: relayStatus.Stage.String(), 98 } 99 } 100 // add error if some error happen 101 if workerStatus.SourceStatus.Result != nil && len(workerStatus.SourceStatus.Result.Errors) > 0 { 102 var errorMsgs string 103 for _, err := range workerStatus.SourceStatus.Result.Errors { 104 errorMsgs += fmt.Sprintf("%s\n", err.Message) 105 } 106 sourceStatus.ErrorMsg = &errorMsgs 107 } 108 sourceStatusList[i] = sourceStatus 109 } 110 return sourceStatusList, nil 111 } 112 113 func (s *Server) createSource(ctx context.Context, req openapi.CreateSourceRequest) (*openapi.Source, error) { 114 cfg := config.OpenAPISourceToSourceCfg(req.Source) 115 if err := CheckAndAdjustSourceConfigFunc(ctx, cfg); err != nil { 116 return nil, err 117 } 118 119 var err error 120 if req.WorkerName == nil { 121 err = s.scheduler.AddSourceCfg(cfg) 122 } else { 123 err = s.scheduler.AddSourceCfgWithWorker(cfg, *req.WorkerName) 124 } 125 if err != nil { 126 return nil, err 127 } 128 // TODO: refine relay logic https://github.com/pingcap/tiflow/issues/4985 129 if cfg.EnableRelay { 130 return &req.Source, s.enableRelay(ctx, req.Source.SourceName, openapi.EnableRelayRequest{}) 131 } 132 return &req.Source, nil 133 } 134 135 func (s *Server) updateSource(ctx context.Context, sourceName string, req openapi.UpdateSourceRequest) (*openapi.Source, error) { 136 // TODO: support dynamic updates 137 oldCfg := s.scheduler.GetSourceCfgByID(sourceName) 138 if oldCfg == nil { 139 return nil, terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 140 } 141 newCfg := config.OpenAPISourceToSourceCfg(req.Source) 142 143 // update's request will be no password when user doesn't input password and wants to use old password. 144 if req.Source.Password == nil { 145 newCfg.From.Password = oldCfg.From.Password 146 } 147 148 if err := CheckAndAdjustSourceConfigFunc(ctx, newCfg); err != nil { 149 return nil, err 150 } 151 if err := s.scheduler.UpdateSourceCfg(newCfg); err != nil { 152 return nil, err 153 } 154 // when enable filed updated, we need operate task on this source 155 if worker := s.scheduler.GetWorkerBySource(sourceName); worker != nil && newCfg.Enable != oldCfg.Enable { 156 stage := pb.Stage_Running 157 taskNameList := s.scheduler.GetTaskNameListBySourceName(sourceName, nil) 158 if !newCfg.Enable { 159 stage = pb.Stage_Stopped 160 } 161 if err := s.scheduler.BatchOperateTaskOnWorker(ctx, worker, taskNameList, sourceName, stage, false); err != nil { 162 return nil, err 163 } 164 } 165 return &req.Source, nil 166 } 167 168 // nolint:unparam 169 func (s *Server) deleteSource(ctx context.Context, sourceName string, force bool) error { 170 if force { 171 for _, taskName := range s.scheduler.GetTaskNameListBySourceName(sourceName, nil) { 172 if err := s.scheduler.RemoveSubTasks(taskName, sourceName); err != nil { 173 return err 174 } 175 } 176 } 177 return s.scheduler.RemoveSourceCfg(sourceName) 178 } 179 180 func (s *Server) getSource(ctx context.Context, sourceName string, req openapi.DMAPIGetSourceParams) (*openapi.Source, error) { 181 sourceCfg := s.scheduler.GetSourceCfgByID(sourceName) 182 if sourceCfg == nil { 183 return nil, terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 184 } 185 source := config.SourceCfgToOpenAPISource(sourceCfg) 186 if req.WithStatus != nil && *req.WithStatus { 187 var statusList []openapi.SourceStatus 188 statusList, err := s.getSourceStatus(ctx, sourceName) 189 if err != nil { 190 return nil, err 191 } 192 source.StatusList = &statusList 193 // add task name list 194 taskNameList := openapi.TaskNameList{} 195 taskNameList = append(taskNameList, s.scheduler.GetTaskNameListBySourceName(sourceName, nil)...) 196 source.TaskNameList = &taskNameList 197 } 198 return &source, nil 199 } 200 201 func (s *Server) getSourceStatus(ctx context.Context, sourceName string) ([]openapi.SourceStatus, error) { 202 return s.getSourceStatusListFromWorker(ctx, sourceName, true) 203 } 204 205 func (s *Server) listSource(ctx context.Context, req openapi.DMAPIGetSourceListParams) ([]openapi.Source, error) { 206 sourceCfgM := s.scheduler.GetSourceCfgs() 207 openapiSourceList := make([]openapi.Source, 0, len(sourceCfgM)) 208 // fill status and filter 209 for _, sourceCfg := range sourceCfgM { 210 // filter by enable_relay 211 // TODO(ehco),maybe worker should use sourceConfig.EnableRelay to determine whether start relay 212 if req.EnableRelay != nil { 213 relayWorkers, err := s.scheduler.GetRelayWorkers(sourceCfg.SourceID) 214 if err != nil { 215 return nil, err 216 } 217 if (*req.EnableRelay && len(relayWorkers) == 0) || (!*req.EnableRelay && len(relayWorkers) > 0) { 218 continue 219 } 220 } 221 source := config.SourceCfgToOpenAPISource(sourceCfg) 222 if req.WithStatus != nil && *req.WithStatus { 223 sourceStatusList, err := s.getSourceStatus(ctx, source.SourceName) 224 if err != nil { 225 return nil, err 226 } 227 source.StatusList = &sourceStatusList 228 // add task name list 229 taskNameList := openapi.TaskNameList{} 230 taskNameList = append(taskNameList, s.scheduler.GetTaskNameListBySourceName(sourceCfg.SourceID, nil)...) 231 source.TaskNameList = &taskNameList 232 } 233 openapiSourceList = append(openapiSourceList, source) 234 } 235 return openapiSourceList, nil 236 } 237 238 // nolint:unparam 239 func (s *Server) enableRelay(ctx context.Context, sourceName string, req openapi.EnableRelayRequest) error { 240 sourceCfg := s.scheduler.GetSourceCfgByID(sourceName) 241 if sourceCfg == nil { 242 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 243 } 244 if req.WorkerNameList == nil { 245 worker := s.scheduler.GetWorkerBySource(sourceName) 246 if worker == nil { 247 return terror.ErrWorkerNoStart.Generate() 248 } 249 req.WorkerNameList = &openapi.WorkerNameList{worker.BaseInfo().Name} 250 } 251 252 needUpdate := false 253 // update relay related in source cfg 254 if req.RelayBinlogName != nil && sourceCfg.RelayBinLogName != *req.RelayBinlogName { 255 sourceCfg.RelayBinLogName = *req.RelayBinlogName 256 needUpdate = true 257 } 258 if req.RelayBinlogGtid != nil && sourceCfg.RelayBinlogGTID != *req.RelayBinlogGtid { 259 sourceCfg.RelayBinlogGTID = *req.RelayBinlogGtid 260 needUpdate = true 261 } 262 if req.RelayDir != nil && sourceCfg.RelayDir != *req.RelayDir { 263 sourceCfg.RelayDir = *req.RelayDir 264 needUpdate = true 265 } 266 if needUpdate { 267 // update current source relay config before start relay 268 if err := s.scheduler.UpdateSourceCfg(sourceCfg); err != nil { 269 return err 270 } 271 } 272 return s.scheduler.StartRelay(sourceName, *req.WorkerNameList) 273 } 274 275 // nolint:unparam 276 func (s *Server) disableRelay(ctx context.Context, sourceName string, req openapi.DisableRelayRequest) error { 277 sourceCfg := s.scheduler.GetSourceCfgByID(sourceName) 278 if sourceCfg == nil { 279 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 280 } 281 if req.WorkerNameList == nil { 282 worker := s.scheduler.GetWorkerBySource(sourceName) 283 if worker == nil { 284 return terror.ErrWorkerNoStart.Generate() 285 } 286 req.WorkerNameList = &openapi.WorkerNameList{worker.BaseInfo().Name} 287 } 288 return s.scheduler.StopRelay(sourceName, *req.WorkerNameList) 289 } 290 291 func (s *Server) purgeRelay(ctx context.Context, sourceName string, req openapi.PurgeRelayRequest) error { 292 purgeReq := &workerrpc.Request{ 293 Type: workerrpc.CmdPurgeRelay, 294 PurgeRelay: &pb.PurgeRelayRequest{Filename: req.RelayBinlogName}, 295 } 296 if req.RelayDir != nil { 297 purgeReq.PurgeRelay.SubDir = *req.RelayDir 298 } 299 // NOTE not all worker that enabled relay is recorded in scheduler, we need refine this later 300 workers, err := s.scheduler.GetRelayWorkers(sourceName) 301 if err != nil { 302 return err 303 } 304 if len(workers) == 0 { 305 return terror.ErrOpenAPICommonError.Generatef("relay worker for source %s not found, please `enable-relay` first", sourceName) 306 } 307 for _, w := range workers { 308 resp, err := w.SendRequest(ctx, purgeReq, s.cfg.RPCTimeout) 309 if err != nil { 310 return err 311 } 312 if resp.PurgeRelay.Msg != "" { 313 return terror.ErrOpenAPICommonError.Generate(resp.PurgeRelay.Msg) 314 } 315 } 316 return nil 317 } 318 319 func (s *Server) enableSource(ctx context.Context, sourceName string) error { 320 cfg := s.scheduler.GetSourceCfgByID(sourceName) 321 if cfg == nil { 322 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 323 } 324 worker := s.scheduler.GetWorkerBySource(sourceName) 325 if worker == nil { 326 return terror.ErrWorkerNoStart.Generate() 327 } 328 if cfg.Enable { 329 return nil 330 } 331 cfg.Enable = true 332 if err := s.scheduler.UpdateSourceCfg(cfg); err != nil { 333 return err 334 } 335 taskNameList := s.scheduler.GetTaskNameListBySourceName(sourceName, nil) 336 return s.scheduler.BatchOperateTaskOnWorker(ctx, worker, taskNameList, sourceName, pb.Stage_Running, false) 337 } 338 339 func (s *Server) disableSource(ctx context.Context, sourceName string) error { 340 cfg := s.scheduler.GetSourceCfgByID(sourceName) 341 if cfg == nil { 342 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 343 } 344 if !cfg.Enable { 345 return nil 346 } 347 worker := s.scheduler.GetWorkerBySource(sourceName) 348 if worker == nil { 349 // no need to stop task if the source is not running 350 cfg.Enable = false 351 return s.scheduler.UpdateSourceCfg(cfg) 352 } 353 taskNameList := s.scheduler.GetTaskNameListBySourceName(sourceName, nil) 354 if err := s.scheduler.BatchOperateTaskOnWorker(ctx, worker, taskNameList, sourceName, pb.Stage_Stopped, false); err != nil { 355 return err 356 } 357 cfg.Enable = false 358 return s.scheduler.UpdateSourceCfg(cfg) 359 } 360 361 func (s *Server) transferSource(ctx context.Context, sourceName, workerName string) error { 362 return s.scheduler.TransferSource(ctx, sourceName, workerName) 363 } 364 365 func (s *Server) checkTask(ctx context.Context, subtaskCfgList []*config.SubTaskConfig, errCnt, warnCnt int64) (string, error) { 366 // TODO(ehco) no api for this task now 367 return checker.CheckSyncConfigFunc(ctx, subtaskCfgList, errCnt, warnCnt) 368 } 369 370 func (s *Server) checkOpenAPITaskBeforeOperate(ctx context.Context, task *openapi.Task) ([]*config.SubTaskConfig, string, error) { 371 // prepare target db config 372 toDBCfg := config.GetTargetDBCfgFromOpenAPITask(task) 373 if err := AdjustTargetDBSessionCfg(ctx, toDBCfg); err != nil { 374 return nil, "", err 375 } 376 // prepare source db config source name -> source config 377 sourceCfgMap := make(map[string]*config.SourceConfig) 378 for _, cfg := range task.SourceConfig.SourceConf { 379 if sourceCfg := s.scheduler.GetSourceCfgByID(cfg.SourceName); sourceCfg != nil { 380 sourceCfgMap[cfg.SourceName] = sourceCfg 381 } else { 382 return nil, "", terror.ErrSchedulerSourceCfgNotExist.Generate(cfg.SourceName) 383 } 384 } 385 // generate sub task configs 386 subTaskConfigList, err := config.OpenAPITaskToSubTaskConfigs(task, toDBCfg, sourceCfgMap) 387 if err != nil { 388 return nil, "", err 389 } 390 stCfgsForCheck, err := s.generateSubTasksForCheck(subTaskConfigList) 391 if err != nil { 392 return nil, "", err 393 } 394 // check subtask config 395 msg, err := s.checkTask(ctx, stCfgsForCheck, common.DefaultErrorCnt, common.DefaultWarnCnt) 396 if err != nil { 397 return nil, "", terror.WithClass(err, terror.ClassDMMaster) 398 } 399 return subTaskConfigList, msg, nil 400 } 401 402 func (s *Server) createTask(ctx context.Context, req openapi.CreateTaskRequest) (*openapi.OperateTaskResponse, error) { 403 task := &req.Task 404 if err := task.Adjust(); err != nil { 405 return nil, err 406 } 407 subTaskConfigList, msg, err := s.checkOpenAPITaskBeforeOperate(ctx, task) 408 if err != nil { 409 return nil, err 410 } 411 res := &openapi.OperateTaskResponse{ 412 Task: *task, 413 CheckResult: msg, 414 } 415 return res, s.scheduler.AddSubTasks(false, pb.Stage_Stopped, subtaskCfgPointersToInstances(subTaskConfigList...)...) 416 } 417 418 func (s *Server) updateTask(ctx context.Context, req openapi.UpdateTaskRequest) (*openapi.OperateTaskResponse, error) { 419 task := &req.Task 420 if err := task.Adjust(); err != nil { 421 return nil, err 422 } 423 subTaskConfigList, msg, err := s.checkOpenAPITaskBeforeOperate(ctx, task) 424 if err != nil { 425 return nil, err 426 } 427 res := &openapi.OperateTaskResponse{ 428 Task: *task, 429 CheckResult: msg, 430 } 431 return res, s.scheduler.UpdateSubTasks(ctx, subtaskCfgPointersToInstances(subTaskConfigList...)...) 432 } 433 434 func (s *Server) deleteTask(ctx context.Context, taskName string, force bool) error { 435 // check if there is running task 436 var task *openapi.Task 437 var err error 438 if !force { 439 task, err = s.getTask(ctx, taskName, openapi.DMAPIGetTaskParams{}) 440 if err != nil { 441 return err 442 } 443 for _, sourceConf := range task.SourceConfig.SourceConf { 444 stage := s.scheduler.GetExpectSubTaskStage(taskName, sourceConf.SourceName) 445 // TODO delete openapi.TaskStagePasused when use openapi to impl dmctl 446 if stage.Expect != pb.Stage_Paused && stage.Expect != pb.Stage_Stopped { 447 return terror.ErrOpenAPICommonError.Generatef("task %s have running subtasks, please stop them or delete task with force.", taskName) 448 } 449 } 450 } else { 451 task, err = s.getTask(ctx, taskName, openapi.DMAPIGetTaskParams{}) 452 if err != nil { 453 return err 454 } 455 } 456 457 // remove meta 458 release, err := s.scheduler.AcquireSubtaskLatch(taskName) 459 if err != nil { 460 return terror.ErrSchedulerLatchInUse.Generate("RemoveMeta", taskName) 461 } 462 defer release() 463 464 ignoreCannotConnectError := func(err error) bool { 465 if err == nil { 466 return true 467 } 468 if force && strings.Contains(err.Error(), "connect: connection refused") { 469 log.L().Warn("connect downstream error when fore delete task", zap.Error(err)) 470 return true 471 } 472 return false 473 } 474 475 toDBCfg := config.GetTargetDBCfgFromOpenAPITask(task) 476 if adjustErr := AdjustTargetDBSessionCfg(ctx, toDBCfg); adjustErr != nil { 477 if !ignoreCannotConnectError(adjustErr) { 478 return adjustErr 479 } 480 } 481 metaSchema := *task.MetaSchema 482 err = s.removeMetaData(ctx, taskName, metaSchema, toDBCfg) 483 if err != nil { 484 if !ignoreCannotConnectError(err) { 485 return terror.Annotate(err, "while removing metadata") 486 } 487 } 488 release() 489 sourceNameList := s.getTaskSourceNameList(taskName) 490 // delete subtask on worker 491 return s.scheduler.RemoveSubTasks(taskName, sourceNameList...) 492 } 493 494 func (s *Server) getTask(ctx context.Context, taskName string, req openapi.DMAPIGetTaskParams) (*openapi.Task, error) { 495 subTaskConfigM := s.scheduler.GetSubTaskCfgsByTask(taskName) 496 if subTaskConfigM == nil { 497 return nil, terror.ErrSchedulerTaskNotExist.Generate(taskName) 498 } 499 subTaskConfigList := make([]*config.SubTaskConfig, 0, len(subTaskConfigM)) 500 for sourceName := range subTaskConfigM { 501 subTaskConfigList = append(subTaskConfigList, subTaskConfigM[sourceName]) 502 } 503 task := config.SubTaskConfigsToOpenAPITask(subTaskConfigList) 504 if req.WithStatus != nil && *req.WithStatus { 505 subTaskStatusList, err := s.getTaskStatus(ctx, task.Name, openapi.DMAPIGetTaskStatusParams{}) 506 if err != nil { 507 return nil, err 508 } 509 task.StatusList = &subTaskStatusList 510 } 511 return task, nil 512 } 513 514 func (s *Server) getTaskStatus(ctx context.Context, taskName string, req openapi.DMAPIGetTaskStatusParams) ([]openapi.SubTaskStatus, error) { 515 if req.SourceNameList == nil || len(*req.SourceNameList) == 0 { 516 sourceNameList := openapi.SourceNameList(s.getTaskSourceNameList(taskName)) 517 req.SourceNameList = &sourceNameList 518 } 519 workerStatusList := s.getStatusFromWorkers(ctx, *req.SourceNameList, taskName, true) 520 subTaskStatusList := make([]openapi.SubTaskStatus, 0, len(workerStatusList)) 521 522 handleProcessError := func(err *pb.ProcessError) string { 523 errorMsg := fmt.Sprintf("[code=%d:class=%s:scope=%s:level=%s], Message: %s", err.ErrCode, err.ErrClass, err.ErrScope, err.ErrLevel, err.Message) 524 if err.RawCause != "" { 525 errorMsg = fmt.Sprintf("%s, RawCause: %s", errorMsg, err.RawCause) 526 } 527 if err.Workaround != "" { 528 errorMsg = fmt.Sprintf("%s, Workaround: %s", errorMsg, err.Workaround) 529 } 530 errorMsg = fmt.Sprintf("%s.", errorMsg) 531 return errorMsg 532 } 533 534 for _, workerStatus := range workerStatusList { 535 if workerStatus == nil || workerStatus.SourceStatus == nil { 536 // this should not happen unless the rpc in the worker server has been modified 537 return nil, terror.ErrOpenAPICommonError.New("worker's query-status response is nil") 538 } 539 sourceStatus := workerStatus.SourceStatus 540 openapiSubTaskStatus := openapi.SubTaskStatus{ 541 Name: taskName, 542 SourceName: sourceStatus.GetSource(), 543 WorkerName: sourceStatus.GetWorker(), 544 } 545 if !workerStatus.Result { 546 openapiSubTaskStatus.ErrorMsg = &workerStatus.Msg 547 subTaskStatusList = append(subTaskStatusList, openapiSubTaskStatus) 548 continue 549 } 550 if len(workerStatus.SubTaskStatus) == 0 { 551 // this should not happen unless the rpc in the worker server has been modified 552 return nil, terror.ErrOpenAPICommonError.New("worker's query-status response is nil") 553 } 554 subTaskStatus := workerStatus.SubTaskStatus[0] 555 if subTaskStatus == nil { 556 // this should not happen unless the rpc in the worker server has been modified 557 return nil, terror.ErrOpenAPICommonError.New("worker's query-status response is nil") 558 } 559 openapiSubTaskStatus.Stage = openapi.TaskStage(subTaskStatus.GetStage().String()) 560 openapiSubTaskStatus.Unit = subTaskStatus.GetUnit().String() 561 openapiSubTaskStatus.UnresolvedDdlLockId = &subTaskStatus.UnresolvedDDLLockID 562 // add load status 563 if loadS := subTaskStatus.GetLoad(); loadS != nil { 564 openapiSubTaskStatus.LoadStatus = &openapi.LoadStatus{ 565 FinishedBytes: loadS.FinishedBytes, 566 MetaBinlog: loadS.MetaBinlog, 567 MetaBinlogGtid: loadS.MetaBinlogGTID, 568 Progress: loadS.Progress, 569 TotalBytes: loadS.TotalBytes, 570 } 571 } 572 // add sync status 573 if syncerS := subTaskStatus.GetSync(); syncerS != nil { 574 openapiSubTaskStatus.SyncStatus = &openapi.SyncStatus{ 575 BinlogType: syncerS.GetBinlogType(), 576 BlockingDdls: syncerS.GetBlockingDDLs(), 577 MasterBinlog: syncerS.GetMasterBinlog(), 578 MasterBinlogGtid: syncerS.GetMasterBinlogGtid(), 579 RecentTps: syncerS.RecentTps, 580 SecondsBehindMaster: syncerS.SecondsBehindMaster, 581 Synced: syncerS.Synced, 582 SyncerBinlog: syncerS.SyncerBinlog, 583 SyncerBinlogGtid: syncerS.SyncerBinlogGtid, 584 TotalEvents: syncerS.TotalEvents, 585 TotalTps: syncerS.TotalTps, 586 } 587 if unResolvedGroups := syncerS.GetUnresolvedGroups(); len(unResolvedGroups) > 0 { 588 openapiSubTaskStatus.SyncStatus.UnresolvedGroups = make([]openapi.ShardingGroup, len(unResolvedGroups)) 589 for i, unResolvedGroup := range unResolvedGroups { 590 openapiSubTaskStatus.SyncStatus.UnresolvedGroups[i] = openapi.ShardingGroup{ 591 DdlList: unResolvedGroup.DDLs, 592 FirstLocation: unResolvedGroup.FirstLocation, 593 Synced: unResolvedGroup.Synced, 594 Target: unResolvedGroup.Target, 595 Unsynced: unResolvedGroup.Unsynced, 596 } 597 } 598 } 599 } 600 // add dump status 601 if dumpS := subTaskStatus.GetDump(); dumpS != nil { 602 openapiSubTaskStatus.DumpStatus = &openapi.DumpStatus{ 603 CompletedTables: dumpS.CompletedTables, 604 EstimateTotalRows: dumpS.EstimateTotalRows, 605 FinishedBytes: dumpS.FinishedBytes, 606 FinishedRows: dumpS.FinishedRows, 607 TotalTables: dumpS.TotalTables, 608 } 609 } 610 // add error if some error happens 611 if subTaskStatus.Result != nil && len(subTaskStatus.Result.Errors) > 0 { 612 var errorMsgs string 613 for _, err := range subTaskStatus.Result.Errors { 614 errorMsgs += fmt.Sprintf("%s\n", handleProcessError(err)) 615 } 616 openapiSubTaskStatus.ErrorMsg = &errorMsgs 617 } 618 subTaskStatusList = append(subTaskStatusList, openapiSubTaskStatus) 619 } 620 return subTaskStatusList, nil 621 } 622 623 func (s *Server) listTask(ctx context.Context, req openapi.DMAPIGetTaskListParams) ([]openapi.Task, error) { 624 subTaskConfigMap := s.scheduler.GetALlSubTaskCfgs() 625 taskList := config.SubTaskConfigsToOpenAPITaskList(subTaskConfigMap) 626 taskArray := make([]openapi.Task, 0, len(taskList)) 627 628 if req.Stage != nil || (req.WithStatus != nil && *req.WithStatus) { 629 for idx := range taskList { 630 subTaskStatusList, err := s.getTaskStatus(ctx, taskList[idx].Name, openapi.DMAPIGetTaskStatusParams{}) 631 if err != nil { 632 return taskArray, err 633 } 634 taskList[idx].StatusList = &subTaskStatusList 635 } 636 } 637 for idx, task := range taskList { 638 filtered := false 639 // filter by stage 640 if task.StatusList != nil && req.Stage != nil { 641 for _, status := range *task.StatusList { 642 if status.Stage != *req.Stage { 643 filtered = true 644 break 645 } 646 } 647 } 648 // filter by source 649 if req.SourceNameList != nil { 650 if len(task.SourceConfig.SourceConf) != len(*req.SourceNameList) { 651 filtered = true 652 } 653 sourceNameMap := make(map[string]struct{}) 654 for _, sourceName := range *req.SourceNameList { 655 sourceNameMap[sourceName] = struct{}{} 656 } 657 for _, sourceConf := range task.SourceConfig.SourceConf { 658 if _, ok := sourceNameMap[sourceConf.SourceName]; !ok { 659 filtered = true 660 break 661 } 662 } 663 } 664 if !filtered { 665 // remove status 666 if req.WithStatus == nil || (req.WithStatus != nil && !*req.WithStatus) { 667 taskList[idx].StatusList = nil 668 } 669 taskArray = append(taskArray, *taskList[idx]) 670 } 671 } 672 return taskArray, nil 673 } 674 675 func (s *Server) startTask(ctx context.Context, taskName string, req openapi.StartTaskRequest) error { 676 // start all subtasks for this task 677 if req.SourceNameList == nil || len(*req.SourceNameList) == 0 { 678 sourceNameList := openapi.SourceNameList(s.getTaskSourceNameList(taskName)) 679 req.SourceNameList = &sourceNameList 680 } 681 subTaskConfigM := s.scheduler.GetSubTaskCfgsByTask(taskName) 682 needStartSubTaskList := make([]*config.SubTaskConfig, 0, len(*req.SourceNameList)) 683 for _, sourceName := range *req.SourceNameList { 684 subTaskCfg, ok := subTaskConfigM[sourceName] 685 if !ok { 686 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 687 } 688 // start task check. incremental task need to specify meta or start time 689 if subTaskCfg.Meta == nil && subTaskCfg.Mode == config.ModeIncrement && req.StartTime == nil { 690 return terror.ErrConfigMetadataNotSet.Generate(sourceName, config.ModeIncrement) 691 } 692 cfg := s.scheduler.GetSourceCfgByID(sourceName) 693 if cfg == nil { 694 return terror.ErrSchedulerSourceCfgNotExist.Generate(sourceName) 695 } 696 if !cfg.Enable { 697 return terror.ErrMasterStartTask.Generate(taskName, fmt.Sprintf("source: %s is not enabled", sourceName)) 698 } 699 needStartSubTaskList = append(needStartSubTaskList, subTaskCfg) 700 } 701 if len(needStartSubTaskList) == 0 { 702 return nil 703 } 704 705 var ( 706 release scheduler.ReleaseFunc 707 err error 708 ) 709 // removeMeta 710 if req.RemoveMeta != nil && *req.RemoveMeta { 711 // use same latch for remove-meta and start-task 712 release, err = s.scheduler.AcquireSubtaskLatch(taskName) 713 if err != nil { 714 return terror.ErrSchedulerLatchInUse.Generate("RemoveMeta", taskName) 715 } 716 defer release() 717 metaSchema := needStartSubTaskList[0].MetaSchema 718 targetDB := needStartSubTaskList[0].To 719 err = s.removeMetaData(ctx, taskName, metaSchema, &targetDB) 720 if err != nil { 721 return terror.Annotate(err, "while removing metadata") 722 } 723 } 724 725 // handle task cli args 726 cliArgs, err := config.OpenAPIStartTaskReqToTaskCliArgs(req) 727 if err != nil { 728 return terror.Annotate(err, "while converting task command line arguments") 729 } 730 731 if err = handleCliArgs(s.etcdClient, taskName, *req.SourceNameList, cliArgs); err != nil { 732 return err 733 } 734 if release != nil { 735 release() 736 } 737 738 return s.scheduler.UpdateExpectSubTaskStage(pb.Stage_Running, taskName, *req.SourceNameList...) 739 } 740 741 // nolint:unparam 742 func (s *Server) stopTask(ctx context.Context, taskName string, req openapi.StopTaskRequest) error { 743 // all subtasks for this task 744 if req.SourceNameList == nil || len(*req.SourceNameList) == 0 { 745 sourceNameList := openapi.SourceNameList(s.getTaskSourceNameList(taskName)) 746 req.SourceNameList = &sourceNameList 747 } 748 // handle task cli args 749 cliArgs, err := config.OpenAPIStopTaskReqToTaskCliArgs(req) 750 if err != nil { 751 return terror.Annotate(err, "while converting task command line arguments") 752 } 753 if err = handleCliArgs(s.etcdClient, taskName, *req.SourceNameList, cliArgs); err != nil { 754 return err 755 } 756 return s.scheduler.UpdateExpectSubTaskStage(pb.Stage_Stopped, taskName, *req.SourceNameList...) 757 } 758 759 // handleCliArgs handles cli args. 760 // it will try to delete args if cli args is nil. 761 func handleCliArgs(cli *clientv3.Client, taskName string, sources []string, cliArgs *config.TaskCliArgs) error { 762 if cliArgs == nil { 763 err := ha.DeleteTaskCliArgs(cli, taskName, sources) 764 if err != nil { 765 return terror.Annotate(err, "while removing task command line arguments") 766 } 767 } else { 768 err := ha.PutTaskCliArgs(cli, taskName, sources, *cliArgs) 769 if err != nil { 770 return terror.Annotate(err, "while putting task command line arguments") 771 } 772 } 773 return nil 774 } 775 776 // nolint:unparam 777 func (s *Server) convertTaskConfig(ctx context.Context, req openapi.ConverterTaskRequest) (*openapi.Task, *config.TaskConfig, error) { 778 if req.TaskConfigFile != nil { 779 taskCfg := config.NewTaskConfig() 780 if err := taskCfg.RawDecode(*req.TaskConfigFile); err != nil { 781 return nil, nil, err 782 } 783 // clear extra config in MySQLInstance, use cfg.xxConfigName instead otherwise adjust will fail 784 for _, cfg := range taskCfg.MySQLInstances { 785 cfg.Mydumper = nil 786 cfg.Loader = nil 787 cfg.Syncer = nil 788 } 789 if adjustErr := taskCfg.Adjust(); adjustErr != nil { 790 return nil, nil, adjustErr 791 } 792 sourceCfgMap := make(map[string]*config.SourceConfig, len(taskCfg.MySQLInstances)) 793 for _, source := range taskCfg.MySQLInstances { 794 sourceCfg := s.scheduler.GetSourceCfgByID(source.SourceID) 795 if sourceCfg == nil { 796 return nil, nil, terror.ErrConfigSourceIDNotFound.Generate(source.SourceID) 797 } 798 sourceCfgMap[source.SourceID] = sourceCfg 799 } 800 task, err := config.TaskConfigToOpenAPITask(taskCfg, sourceCfgMap) 801 if err != nil { 802 return nil, nil, err 803 } 804 return task, taskCfg, nil 805 } 806 task := req.Task 807 if adjustErr := task.Adjust(); adjustErr != nil { 808 return nil, nil, adjustErr 809 } 810 sourceCfgMap := make(map[string]*config.SourceConfig, len(task.SourceConfig.SourceConf)) 811 for _, cfg := range task.SourceConfig.SourceConf { 812 sourceCfg := s.scheduler.GetSourceCfgByID(cfg.SourceName) 813 if sourceCfg == nil { 814 return nil, nil, terror.ErrConfigSourceIDNotFound.Generate(cfg.SourceName) 815 } 816 sourceCfgMap[sourceCfg.SourceID] = sourceCfg 817 } 818 taskCfg, err := config.OpenAPITaskToTaskConfig(task, sourceCfgMap) 819 if err != nil { 820 return nil, nil, err 821 } 822 return task, taskCfg, nil 823 }