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 }