github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/cmd/cli/cli_changefeed_create.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 cli
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"net/url"
    20  	"strings"
    21  
    22  	"github.com/fatih/color"
    23  	"github.com/pingcap/errors"
    24  	"github.com/pingcap/log"
    25  	v2 "github.com/pingcap/tiflow/cdc/api/v2"
    26  	"github.com/pingcap/tiflow/cdc/model"
    27  	apiv2client "github.com/pingcap/tiflow/pkg/api/v2"
    28  	cmdcontext "github.com/pingcap/tiflow/pkg/cmd/context"
    29  	"github.com/pingcap/tiflow/pkg/cmd/factory"
    30  	"github.com/pingcap/tiflow/pkg/cmd/util"
    31  	"github.com/pingcap/tiflow/pkg/config"
    32  	"github.com/pingcap/tiflow/pkg/filter"
    33  	putil "github.com/pingcap/tiflow/pkg/util"
    34  	"github.com/spf13/cobra"
    35  	"github.com/tikv/client-go/v2/oracle"
    36  	"go.uber.org/zap"
    37  )
    38  
    39  // changefeedCommonOptions defines common changefeed flags.
    40  type changefeedCommonOptions struct {
    41  	noConfirm      bool
    42  	targetTs       uint64
    43  	sinkURI        string
    44  	schemaRegistry string
    45  	configFile     string
    46  	sortEngine     string
    47  	sortDir        string
    48  
    49  	upstreamPDAddrs  string
    50  	upstreamCaPath   string
    51  	upstreamCertPath string
    52  	upstreamKeyPath  string
    53  }
    54  
    55  // newChangefeedCommonOptions creates new changefeed common options.
    56  func newChangefeedCommonOptions() *changefeedCommonOptions {
    57  	return &changefeedCommonOptions{}
    58  }
    59  
    60  // addFlags receives a *cobra.Command reference and binds
    61  // flags related to template printing to it.
    62  func (o *changefeedCommonOptions) addFlags(cmd *cobra.Command) {
    63  	cmd.PersistentFlags().BoolVar(&o.noConfirm, "no-confirm", false, "Don't ask user whether to ignore ineligible table")
    64  	cmd.PersistentFlags().Uint64Var(&o.targetTs, "target-ts", 0, "Target ts of changefeed")
    65  	cmd.PersistentFlags().StringVar(&o.sinkURI, "sink-uri", "", "sink uri")
    66  	cmd.PersistentFlags().StringVar(&o.configFile, "config", "", "Path of the configuration file")
    67  	cmd.PersistentFlags().StringVar(&o.sortEngine, "sort-engine", model.SortUnified, "sort engine used for data sort")
    68  	cmd.PersistentFlags().StringVar(&o.sortDir, "sort-dir", "", "directory used for data sort")
    69  	cmd.PersistentFlags().StringVar(&o.schemaRegistry, "schema-registry", "",
    70  		"Avro Schema Registry URI")
    71  	cmd.PersistentFlags().StringVar(&o.upstreamPDAddrs, "upstream-pd", "",
    72  		"upstream PD address, use ',' to separate multiple PDs")
    73  	cmd.PersistentFlags().StringVar(&o.upstreamCaPath, "upstream-ca", "",
    74  		"CA certificate path for TLS connection to upstream")
    75  	cmd.PersistentFlags().StringVar(&o.upstreamCertPath, "upstream-cert", "",
    76  		"Certificate path for TLS connection to upstream")
    77  	cmd.PersistentFlags().StringVar(&o.upstreamKeyPath, "upstream-key", "",
    78  		"Private key path for TLS connection to upstream")
    79  	_ = cmd.PersistentFlags().MarkHidden("sort-dir")
    80  	// we don't support specify these flags below when cdc version >= 6.2.0
    81  	_ = cmd.PersistentFlags().MarkHidden("sort-engine")
    82  	// we don't support specify there flags below when cdc version <= 6.3.0
    83  	_ = cmd.PersistentFlags().MarkHidden("upstream-pd")
    84  	_ = cmd.PersistentFlags().MarkHidden("upstream-ca")
    85  	_ = cmd.PersistentFlags().MarkHidden("upstream-cert")
    86  	_ = cmd.PersistentFlags().MarkHidden("upstream-key")
    87  }
    88  
    89  // strictDecodeConfig do strictDecodeFile check and only verify the rules for now.
    90  func (o *changefeedCommonOptions) strictDecodeConfig(component string, cfg *config.ReplicaConfig) error {
    91  	err := util.StrictDecodeFile(o.configFile, component, cfg)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	_, err = filter.VerifyTableRules(cfg.Filter)
    97  
    98  	return err
    99  }
   100  
   101  // createChangefeedOptions defines common flags for the `cli changefeed crate` command.
   102  type createChangefeedOptions struct {
   103  	commonChangefeedOptions *changefeedCommonOptions
   104  	apiClient               apiv2client.APIV2Interface
   105  
   106  	changefeedID            string
   107  	namespace               string
   108  	disableGCSafePointCheck bool
   109  	startTs                 uint64
   110  	timezone                string
   111  
   112  	cfg *config.ReplicaConfig
   113  }
   114  
   115  // newCreateChangefeedOptions creates new options for the `cli changefeed create` command.
   116  func newCreateChangefeedOptions(commonChangefeedOptions *changefeedCommonOptions) *createChangefeedOptions {
   117  	return &createChangefeedOptions{
   118  		commonChangefeedOptions: commonChangefeedOptions,
   119  	}
   120  }
   121  
   122  // addFlags receives a *cobra.Command reference and binds
   123  // flags related to template printing to it.
   124  func (o *createChangefeedOptions) addFlags(cmd *cobra.Command) {
   125  	o.commonChangefeedOptions.addFlags(cmd)
   126  	cmd.PersistentFlags().StringVarP(&o.namespace, "namespace", "n", "default", "Replication task (changefeed) Namespace")
   127  	cmd.PersistentFlags().StringVarP(&o.changefeedID, "changefeed-id", "c", "", "Replication task (changefeed) ID")
   128  	cmd.PersistentFlags().BoolVarP(&o.disableGCSafePointCheck, "disable-gc-check", "", false, "Disable GC safe point check")
   129  	cmd.PersistentFlags().Uint64Var(&o.startTs, "start-ts", 0, "Start ts of changefeed")
   130  	cmd.PersistentFlags().StringVar(&o.timezone, "tz", "SYSTEM", "timezone used when checking sink uri (changefeed timezone is determined by cdc server)")
   131  	// we don't support specify these flags below when cdc version >= 6.2.0
   132  	_ = cmd.PersistentFlags().MarkHidden("tz")
   133  }
   134  
   135  // complete adapts from the command line args to the data and client required.
   136  func (o *createChangefeedOptions) complete(f factory.Factory) error {
   137  	client, err := f.APIV2Client()
   138  	if err != nil {
   139  		return err
   140  	}
   141  	o.apiClient = client
   142  	return o.completeReplicaCfg()
   143  }
   144  
   145  // completeCfg complete the replica config from file and cmd flags.
   146  func (o *createChangefeedOptions) completeReplicaCfg() error {
   147  	cfg := config.GetDefaultReplicaConfig()
   148  	if len(o.commonChangefeedOptions.configFile) > 0 {
   149  		if err := o.commonChangefeedOptions.strictDecodeConfig("TiCDC changefeed", cfg); err != nil {
   150  			return err
   151  		}
   152  	}
   153  
   154  	uri, err := url.Parse(o.commonChangefeedOptions.sinkURI)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	err = cfg.ValidateAndAdjust(uri)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	if o.commonChangefeedOptions.schemaRegistry != "" {
   165  		cfg.Sink.SchemaRegistry = putil.AddressOf(o.commonChangefeedOptions.schemaRegistry)
   166  	}
   167  
   168  	switch o.commonChangefeedOptions.sortEngine {
   169  	case model.SortInMemory:
   170  	case model.SortInFile:
   171  	case model.SortUnified:
   172  	default:
   173  		log.Warn("invalid sort-engine, use Unified Sorter by default",
   174  			zap.String("invalidSortEngine", o.commonChangefeedOptions.sortEngine))
   175  		o.commonChangefeedOptions.sortEngine = model.SortUnified
   176  	}
   177  
   178  	if o.disableGCSafePointCheck {
   179  		cfg.CheckGCSafePoint = false
   180  	}
   181  	// Complete cfg.
   182  	o.cfg = cfg
   183  
   184  	return nil
   185  }
   186  
   187  // validate checks that the provided attach options are specified.
   188  func (o *createChangefeedOptions) validate(cmd *cobra.Command) error {
   189  	if o.timezone != "SYSTEM" {
   190  		cmd.Printf(color.HiYellowString("[WARN] --tz is deprecated in changefeed settings.\n"))
   191  	}
   192  
   193  	// user is not allowed to set sort-dir at changefeed level
   194  	if o.commonChangefeedOptions.sortDir != "" {
   195  		cmd.Printf(color.HiYellowString("[WARN] --sort-dir is deprecated in changefeed settings. " +
   196  			"Please use `cdc server --data-dir` to start the cdc server if possible, sort-dir will be set automatically. " +
   197  			"The --sort-dir here will be no-op\n"))
   198  		return errors.New("creating changefeed with `--sort-dir`, it's invalid")
   199  	}
   200  
   201  	switch o.commonChangefeedOptions.sortEngine {
   202  	case model.SortInMemory:
   203  	case model.SortInFile:
   204  	case model.SortUnified:
   205  	default:
   206  		log.Warn("invalid sort-engine, use Unified Sorter by default",
   207  			zap.String("invalidSortEngine", o.commonChangefeedOptions.sortEngine))
   208  		o.commonChangefeedOptions.sortEngine = model.SortUnified
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (o *createChangefeedOptions) getChangefeedConfig() *v2.ChangefeedConfig {
   215  	replicaConfig := v2.ToAPIReplicaConfig(o.cfg)
   216  	upstreamConfig := o.getUpstreamConfig()
   217  	return &v2.ChangefeedConfig{
   218  		ID:            o.changefeedID,
   219  		Namespace:     o.namespace,
   220  		StartTs:       o.startTs,
   221  		TargetTs:      o.commonChangefeedOptions.targetTs,
   222  		SinkURI:       o.commonChangefeedOptions.sinkURI,
   223  		ReplicaConfig: replicaConfig,
   224  		PDConfig:      upstreamConfig.PDConfig,
   225  	}
   226  }
   227  
   228  func (o *createChangefeedOptions) getUpstreamConfig() *v2.UpstreamConfig {
   229  	var (
   230  		pdAddrs  []string
   231  		caPath   string
   232  		keyPath  string
   233  		certPath string
   234  	)
   235  	if o.commonChangefeedOptions.upstreamPDAddrs != "" {
   236  		pdAddrs = strings.Split(o.commonChangefeedOptions.upstreamPDAddrs, ",")
   237  		caPath = o.commonChangefeedOptions.upstreamCaPath
   238  		certPath = o.commonChangefeedOptions.upstreamCertPath
   239  		keyPath = o.commonChangefeedOptions.upstreamKeyPath
   240  	}
   241  	return &v2.UpstreamConfig{
   242  		PDConfig: v2.PDConfig{
   243  			PDAddrs:       pdAddrs,
   244  			CAPath:        caPath,
   245  			CertPath:      certPath,
   246  			KeyPath:       keyPath,
   247  			CertAllowedCN: nil,
   248  		},
   249  	}
   250  }
   251  
   252  // run the `cli changefeed create` command.
   253  func (o *createChangefeedOptions) run(ctx context.Context, cmd *cobra.Command) error {
   254  	tso, err := o.apiClient.Tso().Query(ctx, o.getUpstreamConfig())
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	if o.startTs == 0 {
   260  		o.startTs = oracle.ComposeTS(tso.Timestamp, tso.LogicTime)
   261  	}
   262  
   263  	if !o.commonChangefeedOptions.noConfirm {
   264  		if err = confirmLargeDataGap(cmd, tso.Timestamp, o.startTs, "create"); err != nil {
   265  			return err
   266  		}
   267  	}
   268  
   269  	createChangefeedCfg := o.getChangefeedConfig()
   270  
   271  	verifyTableConfig := &v2.VerifyTableConfig{
   272  		PDConfig: v2.PDConfig{
   273  			PDAddrs:       createChangefeedCfg.PDAddrs,
   274  			CAPath:        createChangefeedCfg.CAPath,
   275  			CertPath:      createChangefeedCfg.CertPath,
   276  			KeyPath:       createChangefeedCfg.KeyPath,
   277  			CertAllowedCN: createChangefeedCfg.CertAllowedCN,
   278  		},
   279  		ReplicaConfig: createChangefeedCfg.ReplicaConfig,
   280  		StartTs:       createChangefeedCfg.StartTs,
   281  		SinkURI:       createChangefeedCfg.SinkURI,
   282  	}
   283  
   284  	tables, err := o.apiClient.Changefeeds().VerifyTable(ctx, verifyTableConfig)
   285  	if err != nil {
   286  		if strings.Contains(err.Error(), "ErrInvalidIgnoreEventType") {
   287  			supportedEventTypes := filter.SupportedEventTypes()
   288  			eventTypesStr := make([]string, 0, len(supportedEventTypes))
   289  			for _, eventType := range supportedEventTypes {
   290  				eventTypesStr = append(eventTypesStr, string(eventType))
   291  			}
   292  			cmd.Println(fmt.Sprintf("Invalid input, 'ignore-event' parameters can only accept [%s]",
   293  				strings.Join(eventTypesStr, ", ")))
   294  		}
   295  		return err
   296  	}
   297  
   298  	ignoreIneligibleTables := false
   299  	if len(tables.IneligibleTables) != 0 {
   300  		if o.cfg.ForceReplicate {
   301  			cmd.Printf("[WARN] Force to replicate some ineligible tables, "+
   302  				"these tables do not have a primary key or a not-null unique key: %#v\n"+
   303  				"[WARN] This may cause data redundancy, "+
   304  				"please refer to the official documentation for details.\n",
   305  				tables.IneligibleTables)
   306  		} else {
   307  			cmd.Printf("[WARN] Some tables are not eligible to replicate, "+
   308  				"because they do not have a primary key or a not-null unique key: %#v\n",
   309  				tables.IneligibleTables)
   310  			if !o.commonChangefeedOptions.noConfirm {
   311  				ignoreIneligibleTables, err = confirmIgnoreIneligibleTables(cmd)
   312  				if err != nil {
   313  					return err
   314  				}
   315  			}
   316  		}
   317  	}
   318  
   319  	if o.commonChangefeedOptions.noConfirm {
   320  		ignoreIneligibleTables = true
   321  	}
   322  
   323  	createChangefeedCfg.ReplicaConfig.IgnoreIneligibleTable = ignoreIneligibleTables
   324  
   325  	info, err := o.apiClient.Changefeeds().Create(ctx, createChangefeedCfg)
   326  	if err != nil {
   327  		if strings.Contains(err.Error(), "ErrInvalidIgnoreEventType") {
   328  			supportedEventTypes := filter.SupportedEventTypes()
   329  			eventTypesStr := make([]string, 0, len(supportedEventTypes))
   330  			for _, eventType := range supportedEventTypes {
   331  				eventTypesStr = append(eventTypesStr, string(eventType))
   332  			}
   333  			cmd.Println(fmt.Sprintf("Invalid input, 'ignore-event' parameters can only accept [%s]",
   334  				strings.Join(eventTypesStr, ", ")))
   335  		}
   336  		return err
   337  	}
   338  	infoStr, err := info.Marshal()
   339  	if err != nil {
   340  		return err
   341  	}
   342  	cmd.Printf("Create changefeed successfully!\nID: %s\nInfo: %s\n", info.ID, infoStr)
   343  	return nil
   344  }
   345  
   346  // newCmdCreateChangefeed creates the `cli changefeed create` command.
   347  func newCmdCreateChangefeed(f factory.Factory) *cobra.Command {
   348  	commonChangefeedOptions := newChangefeedCommonOptions()
   349  
   350  	o := newCreateChangefeedOptions(commonChangefeedOptions)
   351  
   352  	command := &cobra.Command{
   353  		Use:   "create",
   354  		Short: "Create a new replication task (changefeed)",
   355  		Args:  cobra.NoArgs,
   356  		Run: func(cmd *cobra.Command, args []string) {
   357  			ctx := cmdcontext.GetDefaultContext()
   358  
   359  			util.CheckErr(o.complete(f))
   360  			util.CheckErr(o.validate(cmd))
   361  			util.CheckErr(o.run(ctx, cmd))
   362  		},
   363  	}
   364  
   365  	o.addFlags(command)
   366  
   367  	return command
   368  }