github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/api/v2/model_test.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 v2
    15  
    16  import (
    17  	"encoding/json"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/pingcap/tiflow/cdc/model"
    23  	bf "github.com/pingcap/tiflow/pkg/binlog-filter"
    24  	"github.com/pingcap/tiflow/pkg/config"
    25  	"github.com/pingcap/tiflow/pkg/redo"
    26  	"github.com/pingcap/tiflow/pkg/util"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  // note: this is api published default value, not change it
    31  var defaultAPIConfig = &ReplicaConfig{
    32  	MemoryQuota:        config.DefaultChangefeedMemoryQuota,
    33  	CaseSensitive:      false,
    34  	CheckGCSafePoint:   true,
    35  	BDRMode:            util.AddressOf(false),
    36  	EnableSyncPoint:    util.AddressOf(false),
    37  	EnableTableMonitor: util.AddressOf(false),
    38  	SyncPointInterval:  &JSONDuration{10 * time.Minute},
    39  	SyncPointRetention: &JSONDuration{24 * time.Hour},
    40  	Filter: &FilterConfig{
    41  		Rules: []string{"*.*"},
    42  	},
    43  	Mounter: &MounterConfig{
    44  		WorkerNum: 16,
    45  	},
    46  	Sink: &SinkConfig{
    47  		CSVConfig: &CSVConfig{
    48  			Quote:                string(config.DoubleQuoteChar),
    49  			Delimiter:            config.Comma,
    50  			NullString:           config.NULL,
    51  			BinaryEncodingMethod: config.BinaryEncodingBase64,
    52  		},
    53  		EncoderConcurrency:               util.AddressOf(config.DefaultEncoderGroupConcurrency),
    54  		Terminator:                       util.AddressOf(config.CRLF),
    55  		DateSeparator:                    util.AddressOf(config.DateSeparatorDay.String()),
    56  		EnablePartitionSeparator:         util.AddressOf(true),
    57  		EnableKafkaSinkV2:                util.AddressOf(false),
    58  		OnlyOutputUpdatedColumns:         util.AddressOf(false),
    59  		DeleteOnlyOutputHandleKeyColumns: util.AddressOf(false),
    60  		ContentCompatible:                util.AddressOf(false),
    61  		AdvanceTimeoutInSec:              util.AddressOf(uint(150)),
    62  		SendBootstrapIntervalInSec:       util.AddressOf(int64(120)),
    63  		SendBootstrapInMsgCount:          util.AddressOf(int32(10000)),
    64  		SendBootstrapToAllPartition:      util.AddressOf(true),
    65  		DebeziumDisableSchema:            util.AddressOf(false),
    66  		OpenProtocolConfig:               &OpenProtocolConfig{OutputOldValue: true},
    67  		DebeziumConfig:                   &DebeziumConfig{OutputOldValue: true},
    68  	},
    69  	Consistent: &ConsistentConfig{
    70  		Level:                 "none",
    71  		MaxLogSize:            64,
    72  		FlushIntervalInMs:     redo.DefaultFlushIntervalInMs,
    73  		MetaFlushIntervalInMs: redo.DefaultMetaFlushIntervalInMs,
    74  		EncodingWorkerNum:     redo.DefaultEncodingWorkerNum,
    75  		FlushWorkerNum:        redo.DefaultFlushWorkerNum,
    76  		Storage:               "",
    77  		UseFileBackend:        false,
    78  		MemoryUsage: &ConsistentMemoryUsage{
    79  			MemoryQuotaPercentage: 50,
    80  		},
    81  	},
    82  	Scheduler: &ChangefeedSchedulerConfig{
    83  		EnableTableAcrossNodes: config.GetDefaultReplicaConfig().
    84  			Scheduler.EnableTableAcrossNodes,
    85  		RegionThreshold: config.GetDefaultReplicaConfig().
    86  			Scheduler.RegionThreshold,
    87  		WriteKeyThreshold: config.GetDefaultReplicaConfig().
    88  			Scheduler.WriteKeyThreshold,
    89  	},
    90  	Integrity: &IntegrityConfig{
    91  		IntegrityCheckLevel:   config.GetDefaultReplicaConfig().Integrity.IntegrityCheckLevel,
    92  		CorruptionHandleLevel: config.GetDefaultReplicaConfig().Integrity.CorruptionHandleLevel,
    93  	},
    94  	ChangefeedErrorStuckDuration: &JSONDuration{*config.
    95  		GetDefaultReplicaConfig().ChangefeedErrorStuckDuration},
    96  	SyncedStatus: (*SyncedStatusConfig)(config.GetDefaultReplicaConfig().SyncedStatus),
    97  }
    98  
    99  func TestDefaultReplicaConfig(t *testing.T) {
   100  	t.Parallel()
   101  	require.Equal(t, defaultAPIConfig, GetDefaultReplicaConfig())
   102  	cfg := GetDefaultReplicaConfig()
   103  	require.NotNil(t, cfg.Scheduler)
   104  	require.NotNil(t, cfg.Integrity)
   105  	cfg2 := cfg.toInternalReplicaConfigWithOriginConfig(&config.ReplicaConfig{})
   106  	require.Equal(t, config.GetDefaultReplicaConfig(), cfg2)
   107  	cfg3 := ToAPIReplicaConfig(config.GetDefaultReplicaConfig())
   108  	require.Equal(t, cfg, cfg3)
   109  }
   110  
   111  func TestToAPIReplicaConfig(t *testing.T) {
   112  	cfg := config.GetDefaultReplicaConfig()
   113  	cfg.CheckGCSafePoint = false
   114  	cfg.Sink = &config.SinkConfig{
   115  		DispatchRules: []*config.DispatchRule{
   116  			{
   117  				Matcher:        []string{"a", "b", "c"},
   118  				DispatcherRule: "",
   119  				PartitionRule:  "rule",
   120  				TopicRule:      "topic",
   121  			},
   122  		},
   123  		Protocol: util.AddressOf("aaa"),
   124  		ColumnSelectors: []*config.ColumnSelector{
   125  			{
   126  				Matcher: []string{"a", "b", "c"},
   127  				Columns: []string{"a", "b"},
   128  			},
   129  		},
   130  		SchemaRegistry: util.AddressOf("bbb"),
   131  		TxnAtomicity:   util.AddressOf(config.AtomicityLevel("aa")),
   132  	}
   133  	cfg.Consistent = &config.ConsistentConfig{
   134  		Level:             "1",
   135  		MaxLogSize:        99,
   136  		FlushIntervalInMs: 10,
   137  		Storage:           "s3",
   138  	}
   139  	cfg.Filter = &config.FilterConfig{
   140  		Rules:            []string{"a", "b", "c"},
   141  		IgnoreTxnStartTs: []uint64{1, 2, 3},
   142  		EventFilters: []*config.EventFilterRule{{
   143  			Matcher:                  []string{"test.t1", "test.t2"},
   144  			IgnoreEvent:              []bf.EventType{bf.AllDML, bf.AllDDL, bf.AlterTable},
   145  			IgnoreSQL:                []string{"^DROP TABLE", "ADD COLUMN"},
   146  			IgnoreInsertValueExpr:    "c >= 0",
   147  			IgnoreUpdateNewValueExpr: "age <= 55",
   148  			IgnoreUpdateOldValueExpr: "age >= 84",
   149  			IgnoreDeleteValueExpr:    "age > 20",
   150  		}},
   151  	}
   152  	cfg.Mounter = &config.MounterConfig{WorkerNum: 11}
   153  	cfg.Scheduler = &config.ChangefeedSchedulerConfig{
   154  		EnableTableAcrossNodes: true, RegionThreshold: 10001, WriteKeyThreshold: 10001,
   155  	}
   156  	cfg2 := ToAPIReplicaConfig(cfg).ToInternalReplicaConfig()
   157  	require.Equal(t, "", cfg2.Sink.DispatchRules[0].DispatcherRule)
   158  	cfg.Sink.DispatchRules[0].DispatcherRule = ""
   159  	require.Equal(t, cfg, cfg2)
   160  	cfgJSON, err := json.Marshal(ToAPIReplicaConfig(cfg))
   161  	require.Nil(t, err)
   162  	require.False(t, strings.Contains(string(cfgJSON), "-"))
   163  }
   164  
   165  func TestChangefeedInfoClone(t *testing.T) {
   166  	cf1 := &ChangeFeedInfo{}
   167  	cf1.UpstreamID = 1
   168  	cf2, err := cf1.Clone()
   169  	require.Nil(t, err)
   170  	require.Equal(t, cf1, cf2)
   171  	cf2.UpstreamID = 2
   172  	require.Equal(t, uint64(1), cf1.UpstreamID)
   173  }
   174  
   175  func TestToCredential(t *testing.T) {
   176  	t.Parallel()
   177  
   178  	pdCfg := &PDConfig{
   179  		PDAddrs:       nil,
   180  		CAPath:        "test-CAPath",
   181  		CertPath:      "test-CertPath",
   182  		KeyPath:       "test-KeyPath",
   183  		CertAllowedCN: nil,
   184  	}
   185  
   186  	credential := pdCfg.toCredential()
   187  	require.Equal(t, pdCfg.CertPath, credential.CertPath)
   188  	require.Equal(t, pdCfg.CAPath, credential.CAPath)
   189  	require.Equal(t, pdCfg.KeyPath, credential.KeyPath)
   190  	require.Equal(t, len(credential.CertAllowedCN), 0)
   191  
   192  	pdCfg.CertAllowedCN = []string{"test-CertAllowedCN"}
   193  	require.Equal(t, len(credential.CertAllowedCN), 0) // deep copy
   194  
   195  	credential = pdCfg.toCredential()
   196  	require.Equal(t, pdCfg.CertPath, credential.CertPath)
   197  	require.Equal(t, pdCfg.CAPath, credential.CAPath)
   198  	require.Equal(t, pdCfg.KeyPath, credential.KeyPath)
   199  	require.Equal(t, len(credential.CertAllowedCN), 1)
   200  	require.Equal(t, credential.CertAllowedCN[0], pdCfg.CertAllowedCN[0])
   201  }
   202  
   203  func TestEventFilterRuleConvert(t *testing.T) {
   204  	cases := []struct {
   205  		inRule  *config.EventFilterRule
   206  		apiRule EventFilterRule
   207  	}{
   208  		{
   209  			inRule: &config.EventFilterRule{
   210  				Matcher:                  []string{"test.t1", "test.t2"},
   211  				IgnoreEvent:              []bf.EventType{bf.AllDML, bf.AllDDL, bf.AlterTable},
   212  				IgnoreSQL:                []string{"^DROP TABLE", "ADD COLUMN"},
   213  				IgnoreInsertValueExpr:    "c >= 0",
   214  				IgnoreUpdateNewValueExpr: "age <= 55",
   215  				IgnoreUpdateOldValueExpr: "age >= 84",
   216  				IgnoreDeleteValueExpr:    "age > 20",
   217  			},
   218  			apiRule: EventFilterRule{
   219  				Matcher:                  []string{"test.t1", "test.t2"},
   220  				IgnoreEvent:              []string{"all dml", "all ddl", "alter table"},
   221  				IgnoreSQL:                []string{"^DROP TABLE", "ADD COLUMN"},
   222  				IgnoreInsertValueExpr:    "c >= 0",
   223  				IgnoreUpdateNewValueExpr: "age <= 55",
   224  				IgnoreUpdateOldValueExpr: "age >= 84",
   225  				IgnoreDeleteValueExpr:    "age > 20",
   226  			},
   227  		},
   228  	}
   229  	for _, c := range cases {
   230  		require.Equal(t, c.apiRule, ToAPIEventFilterRule(c.inRule))
   231  		require.Equal(t, c.inRule, c.apiRule.ToInternalEventFilterRule())
   232  	}
   233  }
   234  
   235  func TestMarshalChangefeedCommonInfo(t *testing.T) {
   236  	t.Parallel()
   237  
   238  	cfInfo := &ChangefeedCommonInfo{
   239  		ID:        "test-id",
   240  		FeedState: model.StatePending,
   241  	}
   242  
   243  	cfInfoJSON, err := json.Marshal(cfInfo)
   244  	require.Nil(t, err)
   245  	require.False(t, strings.Contains(string(cfInfoJSON), "pending"))
   246  	require.True(t, strings.Contains(string(cfInfoJSON), "warning"))
   247  
   248  	cfInfo = &ChangefeedCommonInfo{
   249  		ID:        "test-id",
   250  		FeedState: model.StateUnInitialized,
   251  	}
   252  
   253  	cfInfoJSON, err = json.Marshal(cfInfo)
   254  	require.Nil(t, err)
   255  	require.True(t, strings.Contains(string(cfInfoJSON), "normal"))
   256  }