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  }