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  }