github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/sink/validator/validator.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 package validator 15 16 import ( 17 "context" 18 "net/url" 19 20 "github.com/pingcap/tiflow/cdc/model" 21 "github.com/pingcap/tiflow/cdc/sink/dmlsink/factory" 22 "github.com/pingcap/tiflow/pkg/config" 23 cerror "github.com/pingcap/tiflow/pkg/errors" 24 "github.com/pingcap/tiflow/pkg/pdutil" 25 "github.com/pingcap/tiflow/pkg/sink" 26 pmysql "github.com/pingcap/tiflow/pkg/sink/mysql" 27 "github.com/pingcap/tiflow/pkg/util" 28 ) 29 30 // Validate sink if given valid parameters. 31 // TODO: For now, we create a real sink instance and validate it. 32 // Maybe we should support the dry-run mode to validate sink. 33 func Validate(ctx context.Context, 34 changefeedID model.ChangeFeedID, 35 sinkURI string, cfg *config.ReplicaConfig, 36 pdClock pdutil.Clock, 37 ) error { 38 uri, err := preCheckSinkURI(sinkURI) 39 if err != nil { 40 return err 41 } 42 43 if err := checkSyncPointSchemeCompatibility(uri, cfg); err != nil { 44 return err 45 } 46 47 if util.GetOrZero(cfg.BDRMode) { 48 err := checkBDRMode(ctx, uri, cfg) 49 if err != nil { 50 return err 51 } 52 } 53 54 ctx, cancel := context.WithCancel(ctx) 55 s, err := factory.New(ctx, changefeedID, sinkURI, cfg, make(chan error), pdClock) 56 if err != nil { 57 cancel() 58 return err 59 } 60 cancel() 61 s.Close() 62 63 return nil 64 } 65 66 // checkSyncPointSchemeCompatibility checks if the sink scheme is compatible 67 // with the syncpoint feature. 68 func checkSyncPointSchemeCompatibility( 69 uri *url.URL, 70 cfg *config.ReplicaConfig, 71 ) error { 72 if util.GetOrZero(cfg.EnableSyncPoint) && 73 !sink.IsMySQLCompatibleScheme(uri.Scheme) { 74 return cerror.ErrSinkURIInvalid. 75 GenWithStack( 76 "sink uri scheme is not supported with syncpoint enabled"+ 77 "sink uri: %s", uri, 78 ) 79 } 80 return nil 81 } 82 83 // preCheckSinkURI do some pre-check for sink URI. 84 // 1. Check if sink URI is empty. 85 // 2. Check if we use correct IPv6 format in URI.(if needed) 86 func preCheckSinkURI(sinkURIStr string) (*url.URL, error) { 87 if sinkURIStr == "" { 88 return nil, cerror.ErrSinkURIInvalid.GenWithStack("sink uri is empty") 89 } 90 91 sinkURI, err := url.Parse(sinkURIStr) 92 if err != nil { 93 return nil, cerror.WrapError(cerror.ErrSinkURIInvalid, err) 94 } 95 96 // Check if we use the correct IPv6 address format. 97 // Notice: We should not check the host name is empty or not, 98 // because we have blackhole sink which has empty host name. 99 // Also notice the host name different from host(host+port). 100 if util.IsIPv6Address(sinkURI.Hostname()) && 101 !util.IsValidIPv6AddressFormatInURI(sinkURI.Host) { 102 return nil, cerror.ErrSinkURIInvalid.GenWithStack("sink uri host is not valid IPv6 address, " + 103 "when using IPv6 address in URI, please use [ipv6-address]:port") 104 } 105 106 return sinkURI, nil 107 } 108 109 func checkBDRMode(ctx context.Context, sinkURI *url.URL, replicaConfig *config.ReplicaConfig) error { 110 maskSinkURI, err := util.MaskSinkURI(sinkURI.String()) 111 if err != nil { 112 return err 113 } 114 115 if !sink.IsMySQLCompatibleScheme(sinkURI.Scheme) { 116 return cerror.ErrSinkURIInvalid. 117 GenWithStack("sink uri scheme is not supported in BDR mode, sink uri: %s", maskSinkURI) 118 } 119 cfg := pmysql.NewConfig() 120 id := model.ChangeFeedID{Namespace: "default", ID: "sink-verify"} 121 err = cfg.Apply(config.GetGlobalServerConfig().TZ, id, sinkURI, replicaConfig) 122 if err != nil { 123 return err 124 } 125 dsn, err := pmysql.GenBasicDSN(sinkURI, cfg) 126 if err != nil { 127 return err 128 } 129 testDB, err := pmysql.GetTestDB(ctx, dsn, pmysql.CreateMySQLDBConn) 130 if err != nil { 131 return err 132 } 133 defer testDB.Close() 134 supported, err := pmysql.CheckIfBDRModeIsSupported(ctx, testDB) 135 if err != nil { 136 return err 137 } 138 if !supported { 139 return cerror.ErrSinkURIInvalid. 140 GenWithStack("downstream database does not support BDR mode, "+ 141 "please check your config, sink uri: %s", maskSinkURI) 142 } 143 return nil 144 }