github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ctl/master/operate_task.go (about) 1 // Copyright 2021 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package master 15 16 import ( 17 "context" 18 "errors" 19 "os" 20 "sort" 21 "strings" 22 "sync" 23 24 "github.com/pingcap/tiflow/dm/ctl/common" 25 "github.com/pingcap/tiflow/dm/pb" 26 "github.com/spf13/cobra" 27 ) 28 29 const ( 30 batchSizeFlag = "batch-size" 31 defaultBatchSize = 5 32 ) 33 34 type batchTaskResult struct { 35 Result bool `json:"result"` 36 Msg string `json:"msg"` 37 Tasks []*operateTaskResult `json:"tasks"` 38 } 39 40 type operateTaskResult struct { 41 Task string `json:"task"` 42 Op string `json:"op"` 43 Result bool `json:"result"` 44 Msg string `json:"msg"` 45 Sources []*pb.CommonWorkerResponse `json:"sources"` 46 } 47 48 func operateTaskFunc(taskOp pb.TaskOp, cmd *cobra.Command) error { 49 argLen := len(cmd.Flags().Args()) 50 if argLen == 0 { 51 // may want to operate tasks bound to a source 52 return operateSourceTaskFunc(taskOp, cmd) 53 } else if argLen > 1 { 54 // can pass at most one task-name/task-conf 55 cmd.SetOut(os.Stdout) 56 common.PrintCmdUsage(cmd) 57 return errors.New("please check output to see error") 58 } 59 60 name := common.GetTaskNameFromArgOrFile(cmd.Flags().Arg(0)) 61 sources, err := common.GetSourceArgs(cmd) 62 if err != nil { 63 return err 64 } 65 66 resp, err := common.OperateTask(taskOp, name, sources) 67 if err != nil { 68 common.PrintLinesf("can not %s task %s", strings.ToLower(taskOp.String()), name) 69 return err 70 } 71 72 common.PrettyPrintResponse(resp) 73 return nil 74 } 75 76 func addOperateSourceTaskFlags(cmd *cobra.Command) { 77 // control workload to dm-cluster for sources with large number of tasks. 78 cmd.Flags().Int(batchSizeFlag, defaultBatchSize, "batch size when operating all (sub)tasks bound to a source") 79 } 80 81 func operateSourceTaskFunc(taskOp pb.TaskOp, cmd *cobra.Command) error { 82 source, batchSize, err := parseOperateSourceTaskParams(cmd) 83 if err != nil { 84 cmd.SetOut(os.Stdout) 85 common.PrintCmdUsage(cmd) 86 return errors.New("please check output to see error") 87 } 88 89 sources := []string{source} 90 ctx, cancel := context.WithTimeout(context.Background(), common.GlobalConfig().RPCTimeout) 91 defer cancel() 92 93 req := pb.QueryStatusListRequest{Sources: sources} 94 resp := &pb.QueryStatusListResponse{} 95 if err := common.SendRequest(ctx, "QueryStatus", &req, &resp); err != nil { 96 common.PrintLinesf("cannot query status of source: %v", sources) 97 return err 98 } 99 100 if !resp.Result || len(resp.Sources) == 0 { 101 common.PrettyPrintInterface(&batchTaskResult{Result: false, Msg: resp.Msg, Tasks: []*operateTaskResult{}}) 102 return nil 103 } 104 105 result := batchOperateTask(taskOp, batchSize, sources, resp.Sources[0].SubTaskStatus) 106 common.PrettyPrintInterface(result) 107 108 return nil 109 } 110 111 func batchOperateTask(taskOp pb.TaskOp, batchSize int, sources []string, subTaskStatus []*pb.SubTaskStatus) *batchTaskResult { 112 result := batchTaskResult{Result: true, Tasks: []*operateTaskResult{}} 113 114 if len(subTaskStatus) < batchSize { 115 batchSize = len(subTaskStatus) 116 } 117 118 workCh := make(chan string) 119 go func() { 120 for _, subTask := range subTaskStatus { 121 workCh <- subTask.Name 122 } 123 close(workCh) 124 }() 125 126 var wg sync.WaitGroup 127 resultCh := make(chan *operateTaskResult, 1) 128 for i := 0; i < batchSize; i++ { 129 wg.Add(1) 130 go func() { 131 defer wg.Done() 132 133 for name := range workCh { 134 taskResult := operateTaskResult{Task: name, Op: taskOp.String()} 135 taskOpResp, err := common.OperateTask(taskOp, name, sources) 136 if err != nil { 137 taskResult.Result = false 138 taskResult.Msg = err.Error() 139 } else { 140 taskResult.Result = taskOpResp.Result 141 taskResult.Msg = taskOpResp.Msg 142 taskResult.Sources = taskOpResp.Sources 143 } 144 resultCh <- &taskResult 145 } 146 }() 147 } 148 149 go func() { 150 wg.Wait() 151 close(resultCh) 152 }() 153 154 for item := range resultCh { 155 result.Tasks = append(result.Tasks, item) 156 } 157 158 sort.Slice(result.Tasks, func(i, j int) bool { 159 return result.Tasks[i].Task < result.Tasks[j].Task 160 }) 161 162 return &result 163 } 164 165 func parseOperateSourceTaskParams(cmd *cobra.Command) (string, int, error) { 166 sources, err := common.GetSourceArgs(cmd) 167 if err != nil { 168 return "", 0, err 169 } 170 if len(sources) == 0 { 171 common.PrintLinesf(`must give one source-name when task-name/task-conf is not specified`) 172 return "", 0, errors.New("missing source") 173 } else if len(sources) > 1 { 174 common.PrintLinesf(`can give only one source-name when task-name/task-conf is not specified`) 175 return "", 0, errors.New("too many source") 176 } 177 batchSize, err := cmd.Flags().GetInt(batchSizeFlag) 178 if err != nil { 179 common.PrintLinesf("error in parse `--" + batchSizeFlag + "`") 180 return "", 0, err 181 } 182 return sources[0], batchSize, nil 183 }