github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/kafka/options_test.go (about)

     1  // Copyright 2023 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 kafka
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"net/url"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/IBM/sarama"
    26  	"github.com/aws/aws-sdk-go/aws"
    27  	"github.com/pingcap/errors"
    28  	"github.com/pingcap/tiflow/cdc/model"
    29  	"github.com/pingcap/tiflow/pkg/config"
    30  	cerror "github.com/pingcap/tiflow/pkg/errors"
    31  	"github.com/pingcap/tiflow/pkg/sink/codec/common"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestCompleteOptions(t *testing.T) {
    36  	options := NewOptions()
    37  
    38  	// Normal config.
    39  	uriTemplate := "kafka://127.0.0.1:9092/kafka-test?kafka-version=2.6.0&max-batch-size=5" +
    40  		"&max-message-bytes=%s&partition-num=1&replication-factor=3" +
    41  		"&kafka-client-id=unit-test&auto-create-topic=false&compression=gzip&required-acks=1"
    42  	maxMessageSize := "4096" // 4kb
    43  	uri := fmt.Sprintf(uriTemplate, maxMessageSize)
    44  	sinkURI, err := url.Parse(uri)
    45  	require.NoError(t, err)
    46  
    47  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
    48  	require.NoError(t, err)
    49  	require.Equal(t, int32(1), options.PartitionNum)
    50  	require.Equal(t, int16(3), options.ReplicationFactor)
    51  	require.Equal(t, "2.6.0", options.Version)
    52  	require.Equal(t, 4096, options.MaxMessageBytes)
    53  	require.Equal(t, WaitForLocal, options.RequiredAcks)
    54  
    55  	// multiple kafka broker endpoints
    56  	uri = "kafka://127.0.0.1:9092,127.0.0.1:9091,127.0.0.1:9090/kafka-test?"
    57  	sinkURI, err = url.Parse(uri)
    58  	require.NoError(t, err)
    59  	options = NewOptions()
    60  	err = options.Apply(model.DefaultChangeFeedID("test"),
    61  		sinkURI, config.GetDefaultReplicaConfig())
    62  	require.NoError(t, err)
    63  	require.Len(t, options.BrokerEndpoints, 3)
    64  
    65  	// Illegal replication-factor.
    66  	uri = "kafka://127.0.0.1:9092/abc?kafka-version=2.6.0&replication-factor=a"
    67  	sinkURI, err = url.Parse(uri)
    68  	require.NoError(t, err)
    69  	options = NewOptions()
    70  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
    71  	require.Regexp(t, ".*invalid syntax.*", errors.Cause(err))
    72  
    73  	// Illegal max-message-bytes.
    74  	uri = "kafka://127.0.0.1:9092/abc?kafka-version=2.6.0&max-message-bytes=a"
    75  	sinkURI, err = url.Parse(uri)
    76  	require.NoError(t, err)
    77  	options = NewOptions()
    78  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
    79  	require.Regexp(t, ".*invalid syntax.*", errors.Cause(err))
    80  
    81  	// Illegal partition-num.
    82  	uri = "kafka://127.0.0.1:9092/abc?kafka-version=2.6.0&partition-num=a"
    83  	sinkURI, err = url.Parse(uri)
    84  	require.NoError(t, err)
    85  	options = NewOptions()
    86  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
    87  	require.Regexp(t, ".*invalid syntax.*", errors.Cause(err))
    88  
    89  	// Out of range partition-num.
    90  	uri = "kafka://127.0.0.1:9092/abc?kafka-version=2.6.0&partition-num=0"
    91  	sinkURI, err = url.Parse(uri)
    92  	require.NoError(t, err)
    93  	options = NewOptions()
    94  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
    95  	require.Regexp(t, ".*invalid partition num.*", errors.Cause(err))
    96  
    97  	// Unknown required-acks.
    98  	uri = "kafka://127.0.0.1:9092/abc?kafka-version=2.6.0&required-acks=3"
    99  	sinkURI, err = url.Parse(uri)
   100  	require.NoError(t, err)
   101  	options = NewOptions()
   102  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
   103  	require.Regexp(t, ".*invalid required acks 3.*", errors.Cause(err))
   104  
   105  	// invalid kafka client id
   106  	uri = "kafka://127.0.0.1:9092/abc?kafka-client-id=^invalid$"
   107  	sinkURI, err = url.Parse(uri)
   108  	require.NoError(t, err)
   109  	options = NewOptions()
   110  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
   111  	require.True(t, cerror.ErrKafkaInvalidClientID.Equal(err))
   112  }
   113  
   114  func TestSetPartitionNum(t *testing.T) {
   115  	options := NewOptions()
   116  	err := options.SetPartitionNum(2)
   117  	require.NoError(t, err)
   118  	require.Equal(t, int32(2), options.PartitionNum)
   119  
   120  	options.PartitionNum = 1
   121  	err = options.SetPartitionNum(2)
   122  	require.NoError(t, err)
   123  	require.Equal(t, int32(1), options.PartitionNum)
   124  
   125  	options.PartitionNum = 3
   126  	err = options.SetPartitionNum(2)
   127  	require.True(t, cerror.ErrKafkaInvalidPartitionNum.Equal(err))
   128  }
   129  
   130  func TestClientID(t *testing.T) {
   131  	testCases := []struct {
   132  		addr         string
   133  		changefeedID string
   134  		configuredID string
   135  		hasError     bool
   136  		expected     string
   137  	}{
   138  		{
   139  			"domain:1234", "123-121-121-121",
   140  			"", false,
   141  			"TiCDC_producer_domain_1234_default_123-121-121-121",
   142  		},
   143  		{
   144  			"127.0.0.1:1234", "123-121-121-121",
   145  			"", false,
   146  			"TiCDC_producer_127.0.0.1_1234_default_123-121-121-121",
   147  		},
   148  		{
   149  			"127.0.0.1:1234?:,\"", "123-121-121-121",
   150  			"", false,
   151  			"TiCDC_producer_127.0.0.1_1234_____default_123-121-121-121",
   152  		},
   153  		{
   154  			"中文", "123-121-121-121",
   155  			"", true, "",
   156  		},
   157  		{
   158  			"127.0.0.1:1234",
   159  			"123-121-121-121", "cdc-changefeed-1", false,
   160  			"cdc-changefeed-1",
   161  		},
   162  	}
   163  	for _, tc := range testCases {
   164  		id, err := NewKafkaClientID(tc.addr,
   165  			model.DefaultChangeFeedID(tc.changefeedID), tc.configuredID)
   166  		if tc.hasError {
   167  			require.Error(t, err)
   168  		} else {
   169  			require.NoError(t, err)
   170  			require.Equal(t, tc.expected, id)
   171  		}
   172  	}
   173  }
   174  
   175  func TestTimeout(t *testing.T) {
   176  	options := NewOptions()
   177  	require.Equal(t, 10*time.Second, options.DialTimeout)
   178  	require.Equal(t, 10*time.Second, options.ReadTimeout)
   179  	require.Equal(t, 10*time.Second, options.WriteTimeout)
   180  
   181  	uri := "kafka://127.0.0.1:9092/kafka-test?dial-timeout=5s&read-timeout=1000ms" +
   182  		"&write-timeout=2m"
   183  	sinkURI, err := url.Parse(uri)
   184  	require.NoError(t, err)
   185  
   186  	err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
   187  	require.NoError(t, err)
   188  
   189  	require.Equal(t, 5*time.Second, options.DialTimeout)
   190  	require.Equal(t, 1000*time.Millisecond, options.ReadTimeout)
   191  	require.Equal(t, 2*time.Minute, options.WriteTimeout)
   192  }
   193  
   194  func TestAdjustConfigTopicNotExist(t *testing.T) {
   195  	// When the topic does not exist, use the broker's configuration to create the topic.
   196  	adminClient := NewClusterAdminClientMockImpl()
   197  	defer adminClient.Close()
   198  
   199  	options := NewOptions()
   200  	options.BrokerEndpoints = []string{"127.0.0.1:9092"}
   201  
   202  	// topic not exist, `max-message-bytes` = `message.max.bytes`
   203  	options.MaxMessageBytes = adminClient.GetBrokerMessageMaxBytes()
   204  	ctx := context.Background()
   205  	err := AdjustOptions(ctx, adminClient, options, "create-random")
   206  	require.NoError(t, err)
   207  
   208  	saramaConfig, err := NewSaramaConfig(ctx, options)
   209  	require.NoError(t, err)
   210  	require.Equal(t, options.MaxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   211  
   212  	realMaxMessageBytes := adminClient.GetBrokerMessageMaxBytes() - maxMessageBytesOverhead
   213  	require.Equal(t, realMaxMessageBytes, options.MaxMessageBytes)
   214  
   215  	// topic not exist, `max-message-bytes` > `message.max.bytes`
   216  	options.MaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() + 1
   217  	err = AdjustOptions(ctx, adminClient, options, "create-random1")
   218  	require.NoError(t, err)
   219  
   220  	saramaConfig, err = NewSaramaConfig(ctx, options)
   221  	require.NoError(t, err)
   222  	require.Equal(t, options.MaxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   223  
   224  	realMaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - maxMessageBytesOverhead
   225  	require.Equal(t, realMaxMessageBytes, options.MaxMessageBytes)
   226  
   227  	// topic not exist, `max-message-bytes` < `message.max.bytes`
   228  	options.MaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - 1
   229  	err = AdjustOptions(ctx, adminClient, options, "create-random2")
   230  	require.NoError(t, err)
   231  
   232  	saramaConfig, err = NewSaramaConfig(ctx, options)
   233  	require.NoError(t, err)
   234  	require.Equal(t, options.MaxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   235  
   236  	realMaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - maxMessageBytesOverhead
   237  	require.Equal(t, realMaxMessageBytes, options.MaxMessageBytes)
   238  }
   239  
   240  func TestAdjustConfigTopicExist(t *testing.T) {
   241  	adminClient := NewClusterAdminClientMockImpl()
   242  	defer adminClient.Close()
   243  
   244  	options := NewOptions()
   245  	options.BrokerEndpoints = []string{"127.0.0.1:9092"}
   246  
   247  	ctx := context.Background()
   248  	// topic exists, `max-message-bytes` = `max.message.bytes`.
   249  	options.MaxMessageBytes = adminClient.GetTopicMaxMessageBytes()
   250  
   251  	err := AdjustOptions(ctx, adminClient, options, adminClient.GetDefaultMockTopicName())
   252  	require.NoError(t, err)
   253  
   254  	saramaConfig, err := NewSaramaConfig(ctx, options)
   255  	require.NoError(t, err)
   256  
   257  	maxMessageBytes := adminClient.GetTopicMaxMessageBytes() - maxMessageBytesOverhead
   258  	require.Equal(t, maxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   259  	require.Equal(t, maxMessageBytes, options.MaxMessageBytes)
   260  
   261  	// topic exists, `max-message-bytes` > `max.message.bytes`
   262  	options.MaxMessageBytes = adminClient.GetTopicMaxMessageBytes() + 1
   263  
   264  	err = AdjustOptions(ctx, adminClient, options, adminClient.GetDefaultMockTopicName())
   265  	require.NoError(t, err)
   266  
   267  	saramaConfig, err = NewSaramaConfig(ctx, options)
   268  	require.NoError(t, err)
   269  
   270  	maxMessageBytes = adminClient.GetTopicMaxMessageBytes() - maxMessageBytesOverhead
   271  	require.Equal(t, maxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   272  	require.Equal(t, maxMessageBytes, options.MaxMessageBytes)
   273  
   274  	// topic exists, `max-message-bytes` < `max.message.bytes`
   275  	options.MaxMessageBytes = adminClient.GetTopicMaxMessageBytes() - 1
   276  
   277  	err = AdjustOptions(ctx, adminClient, options, adminClient.GetDefaultMockTopicName())
   278  	require.NoError(t, err)
   279  
   280  	saramaConfig, err = NewSaramaConfig(ctx, options)
   281  	require.NoError(t, err)
   282  
   283  	maxMessageBytes = adminClient.GetTopicMaxMessageBytes() - maxMessageBytesOverhead
   284  	require.Equal(t, maxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   285  	require.Equal(t, maxMessageBytes, options.MaxMessageBytes)
   286  
   287  	// When the topic exists, but the topic does not have `max.message.bytes`
   288  	// create a topic without `max.message.bytes`
   289  	topicName := "test-topic"
   290  	detail := &TopicDetail{
   291  		Name:          topicName,
   292  		NumPartitions: 3,
   293  	}
   294  	err = adminClient.CreateTopic(context.Background(), detail, false)
   295  	require.NoError(t, err)
   296  
   297  	options.MaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - 1
   298  	err = AdjustOptions(ctx, adminClient, options, topicName)
   299  	require.NoError(t, err)
   300  
   301  	saramaConfig, err = NewSaramaConfig(ctx, options)
   302  	require.NoError(t, err)
   303  
   304  	// since `max.message.bytes` cannot be found, use broker's `message.max.bytes` instead.
   305  	maxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - maxMessageBytesOverhead
   306  	require.Equal(t, maxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   307  
   308  	// When the topic exists, but the topic doesn't have `max.message.bytes`
   309  	// `max-message-bytes` > `message.max.bytes`
   310  	options.MaxMessageBytes = adminClient.GetBrokerMessageMaxBytes() + 1
   311  
   312  	err = AdjustOptions(ctx, adminClient, options, topicName)
   313  	require.NoError(t, err)
   314  
   315  	saramaConfig, err = NewSaramaConfig(ctx, options)
   316  	require.NoError(t, err)
   317  
   318  	maxMessageBytes = adminClient.GetBrokerMessageMaxBytes() - maxMessageBytesOverhead
   319  	require.Equal(t, maxMessageBytes, saramaConfig.Producer.MaxMessageBytes)
   320  }
   321  
   322  func TestAdjustConfigMinInsyncReplicas(t *testing.T) {
   323  	adminClient := NewClusterAdminClientMockImpl()
   324  	defer adminClient.Close()
   325  
   326  	options := NewOptions()
   327  	options.BrokerEndpoints = []string{"127.0.0.1:9092"}
   328  
   329  	// Report an error if the replication-factor is less than min.insync.replicas
   330  	// when the topic does not exist.
   331  	adminClient.SetMinInsyncReplicas("2")
   332  
   333  	ctx := context.Background()
   334  	err := AdjustOptions(
   335  		ctx,
   336  		adminClient,
   337  		options,
   338  		"create-new-fail-invalid-min-insync-replicas",
   339  	)
   340  	require.Regexp(
   341  		t,
   342  		".*`replication-factor` 1 is smaller than the `min.insync.replicas` 2 of broker.*",
   343  		errors.Cause(err),
   344  	)
   345  
   346  	// topic not exist, and `min.insync.replicas` not found in broker's configuration
   347  	adminClient.DropBrokerConfig(MinInsyncReplicasConfigName)
   348  	topicName := "no-topic-no-min-insync-replicas"
   349  	err = AdjustOptions(ctx, adminClient, options, "no-topic-no-min-insync-replicas")
   350  	require.Nil(t, err)
   351  	err = adminClient.CreateTopic(context.Background(), &TopicDetail{
   352  		Name:              topicName,
   353  		ReplicationFactor: 1,
   354  	}, false)
   355  	require.ErrorIs(t, err, sarama.ErrPolicyViolation)
   356  
   357  	// Report an error if the replication-factor is less than min.insync.replicas
   358  	// when the topic does exist.
   359  
   360  	// topic exist, but `min.insync.replicas` not found in topic and broker configuration
   361  	topicName = "topic-no-options-entry"
   362  	err = adminClient.CreateTopic(context.Background(), &TopicDetail{
   363  		Name:              topicName,
   364  		ReplicationFactor: 3,
   365  		NumPartitions:     3,
   366  	}, false)
   367  	require.Nil(t, err)
   368  	err = AdjustOptions(ctx, adminClient, options, topicName)
   369  	require.Nil(t, err)
   370  
   371  	// topic found, and have `min.insync.replicas`, but set to 2, larger than `replication-factor`.
   372  	adminClient.SetMinInsyncReplicas("2")
   373  	err = AdjustOptions(ctx, adminClient, options, adminClient.GetDefaultMockTopicName())
   374  	require.Regexp(t,
   375  		".*`replication-factor` 1 is smaller than the `min.insync.replicas` 2 of topic.*",
   376  		errors.Cause(err),
   377  	)
   378  }
   379  
   380  func TestSkipAdjustConfigMinInsyncReplicasWhenRequiredAcksIsNotWailAll(t *testing.T) {
   381  	adminClient := NewClusterAdminClientMockImpl()
   382  	defer adminClient.Close()
   383  
   384  	options := NewOptions()
   385  	options.BrokerEndpoints = []string{"127.0.0.1:9092"}
   386  	options.RequiredAcks = WaitForLocal
   387  
   388  	// Do not report an error if the replication-factor is less than min.insync.replicas(1<2).
   389  	adminClient.SetMinInsyncReplicas("2")
   390  	err := AdjustOptions(
   391  		context.Background(),
   392  		adminClient,
   393  		options,
   394  		"skip-check-min-insync-replicas",
   395  	)
   396  	require.Nil(t, err, "Should not report an error when `required-acks` is not `all`")
   397  }
   398  
   399  func TestCreateProducerFailed(t *testing.T) {
   400  	options := NewOptions()
   401  	options.Version = "invalid"
   402  	options.IsAssignedVersion = true
   403  	saramaConfig, err := NewSaramaConfig(context.Background(), options)
   404  	require.Regexp(t, "invalid version.*", errors.Cause(err))
   405  	require.Nil(t, saramaConfig)
   406  }
   407  
   408  func TestConfigurationCombinations(t *testing.T) {
   409  	combinations := []struct {
   410  		uriTemplate             string
   411  		uriParams               []interface{}
   412  		brokerMessageMaxBytes   string
   413  		topicMaxMessageBytes    string
   414  		expectedMaxMessageBytes string
   415  	}{
   416  		// topic not created,
   417  		// `max-message-bytes` not set, `message.max.bytes` < `max-message-bytes`
   418  		// expected = min(`max-message-bytes`, `message.max.bytes`) = `message.max.bytes`
   419  		{
   420  			"kafka://127.0.0.1:9092/%s",
   421  			[]interface{}{"not-exist-topic"},
   422  			BrokerMessageMaxBytes,
   423  			TopicMaxMessageBytes,
   424  			BrokerMessageMaxBytes,
   425  		},
   426  		// topic not created,
   427  		// `max-message-bytes` not set, `message.max.bytes` = `max-message-bytes`
   428  		// expected = min(`max-message-bytes`, `message.max.bytes`) = `max-message-bytes`
   429  		{
   430  			"kafka://127.0.0.1:9092/%s",
   431  			[]interface{}{"not-exist-topic"},
   432  			strconv.Itoa(config.DefaultMaxMessageBytes),
   433  			TopicMaxMessageBytes,
   434  			strconv.Itoa(config.DefaultMaxMessageBytes),
   435  		},
   436  		// topic not created,
   437  		// `max-message-bytes` not set, broker `message.max.bytes` > `max-message-bytes`
   438  		// expected = min(`max-message-bytes`, `message.max.bytes`) = `max-message-bytes`
   439  		{
   440  			"kafka://127.0.0.1:9092/%s",
   441  			[]interface{}{"no-params"},
   442  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   443  			TopicMaxMessageBytes,
   444  			strconv.Itoa(config.DefaultMaxMessageBytes),
   445  		},
   446  
   447  		// topic not created
   448  		// user set `max-message-bytes` < `message.max.bytes` < default `max-message-bytes`
   449  		{
   450  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   451  			[]interface{}{"not-created-topic", strconv.Itoa(1024*1024 - 1)},
   452  			BrokerMessageMaxBytes,
   453  			TopicMaxMessageBytes,
   454  			strconv.Itoa(1024*1024 - 1),
   455  		},
   456  		// topic not created
   457  		// user set `max-message-bytes` < default `max-message-bytes` < `message.max.bytes`
   458  		{
   459  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   460  			[]interface{}{"not-created-topic", strconv.Itoa(config.DefaultMaxMessageBytes - 1)},
   461  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   462  			TopicMaxMessageBytes,
   463  			strconv.Itoa(config.DefaultMaxMessageBytes - 1),
   464  		},
   465  		// topic not created
   466  		// `message.max.bytes` < user set `max-message-bytes` < default `max-message-bytes`
   467  		{
   468  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   469  			[]interface{}{"not-created-topic", strconv.Itoa(1024*1024 + 1)},
   470  			BrokerMessageMaxBytes,
   471  			TopicMaxMessageBytes,
   472  			BrokerMessageMaxBytes,
   473  		},
   474  		// topic not created
   475  		// `message.max.bytes` < default `max-message-bytes` < user set `max-message-bytes`
   476  		{
   477  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   478  			[]interface{}{"not-created-topic", strconv.Itoa(config.DefaultMaxMessageBytes + 1)},
   479  			BrokerMessageMaxBytes,
   480  			TopicMaxMessageBytes,
   481  			BrokerMessageMaxBytes,
   482  		},
   483  		// topic not created
   484  		// default `max-message-bytes` < user set `max-message-bytes` < `message.max.bytes`
   485  		{
   486  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   487  			[]interface{}{"not-created-topic", strconv.Itoa(config.DefaultMaxMessageBytes + 1)},
   488  			strconv.Itoa(config.DefaultMaxMessageBytes + 2),
   489  			TopicMaxMessageBytes,
   490  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   491  		},
   492  		// topic not created
   493  		// default `max-message-bytes` < `message.max.bytes` < user set `max-message-bytes`
   494  		{
   495  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   496  			[]interface{}{"not-created-topic", strconv.Itoa(config.DefaultMaxMessageBytes + 2)},
   497  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   498  			TopicMaxMessageBytes,
   499  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   500  		},
   501  
   502  		// topic created,
   503  		// `max-message-bytes` not set, topic's `max.message.bytes` < `max-message-bytes`
   504  		// expected = min(`max-message-bytes`, `max.message.bytes`) = `max.message.bytes`
   505  		{
   506  			"kafka://127.0.0.1:9092/%s",
   507  			[]interface{}{DefaultMockTopicName},
   508  			BrokerMessageMaxBytes,
   509  			TopicMaxMessageBytes,
   510  			TopicMaxMessageBytes,
   511  		},
   512  		// `max-message-bytes` not set, topic created,
   513  		// topic's `max.message.bytes` = `max-message-bytes`
   514  		// expected = min(`max-message-bytes`, `max.message.bytes`) = `max-message-bytes`
   515  		{
   516  			"kafka://127.0.0.1:9092/%s",
   517  			[]interface{}{DefaultMockTopicName},
   518  			BrokerMessageMaxBytes,
   519  			strconv.Itoa(config.DefaultMaxMessageBytes),
   520  			strconv.Itoa(config.DefaultMaxMessageBytes),
   521  		},
   522  		// `max-message-bytes` not set, topic created,
   523  		// topic's `max.message.bytes` > `max-message-bytes`
   524  		// expected = min(`max-message-bytes`, `max.message.bytes`) = `max-message-bytes`
   525  		{
   526  			"kafka://127.0.0.1:9092/%s",
   527  			[]interface{}{DefaultMockTopicName},
   528  			BrokerMessageMaxBytes,
   529  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   530  			strconv.Itoa(config.DefaultMaxMessageBytes),
   531  		},
   532  
   533  		// topic created
   534  		// user set `max-message-bytes` < `max.message.bytes` < default `max-message-bytes`
   535  		{
   536  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   537  			[]interface{}{DefaultMockTopicName, strconv.Itoa(1024*1024 - 1)},
   538  			BrokerMessageMaxBytes,
   539  			TopicMaxMessageBytes,
   540  			strconv.Itoa(1024*1024 - 1),
   541  		},
   542  		// topic created
   543  		// user set `max-message-bytes` < default `max-message-bytes` < `max.message.bytes`
   544  		{
   545  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   546  			[]interface{}{
   547  				DefaultMockTopicName,
   548  				strconv.Itoa(config.DefaultMaxMessageBytes - 1),
   549  			},
   550  			BrokerMessageMaxBytes,
   551  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   552  			strconv.Itoa(config.DefaultMaxMessageBytes - 1),
   553  		},
   554  		// topic created
   555  		// `max.message.bytes` < user set `max-message-bytes` < default `max-message-bytes`
   556  		{
   557  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   558  			[]interface{}{DefaultMockTopicName, strconv.Itoa(1024*1024 + 1)},
   559  			BrokerMessageMaxBytes,
   560  			TopicMaxMessageBytes,
   561  			TopicMaxMessageBytes,
   562  		},
   563  		// topic created
   564  		// `max.message.bytes` < default `max-message-bytes` < user set `max-message-bytes`
   565  		{
   566  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   567  			[]interface{}{
   568  				DefaultMockTopicName,
   569  				strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   570  			},
   571  			BrokerMessageMaxBytes,
   572  			TopicMaxMessageBytes,
   573  			TopicMaxMessageBytes,
   574  		},
   575  		// topic created
   576  		// default `max-message-bytes` < user set `max-message-bytes` < `max.message.bytes`
   577  		{
   578  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   579  			[]interface{}{
   580  				DefaultMockTopicName,
   581  				strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   582  			},
   583  			BrokerMessageMaxBytes,
   584  			strconv.Itoa(config.DefaultMaxMessageBytes + 2),
   585  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   586  		},
   587  		// topic created
   588  		// default `max-message-bytes` < `max.message.bytes` < user set `max-message-bytes`
   589  		{
   590  			"kafka://127.0.0.1:9092/%s?max-message-bytes=%s",
   591  			[]interface{}{
   592  				DefaultMockTopicName,
   593  				strconv.Itoa(config.DefaultMaxMessageBytes + 2),
   594  			},
   595  			BrokerMessageMaxBytes,
   596  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   597  			strconv.Itoa(config.DefaultMaxMessageBytes + 1),
   598  		},
   599  	}
   600  
   601  	for _, a := range combinations {
   602  		BrokerMessageMaxBytes = a.brokerMessageMaxBytes
   603  		TopicMaxMessageBytes = a.topicMaxMessageBytes
   604  
   605  		uri := fmt.Sprintf(a.uriTemplate, a.uriParams...)
   606  		sinkURI, err := url.Parse(uri)
   607  		require.Nil(t, err)
   608  
   609  		ctx := context.Background()
   610  		options := NewOptions()
   611  		err = options.Apply(model.DefaultChangeFeedID("test"), sinkURI, config.GetDefaultReplicaConfig())
   612  		require.Nil(t, err)
   613  
   614  		changefeed := model.DefaultChangeFeedID("changefeed-test")
   615  		factory, err := NewMockFactory(options, changefeed)
   616  		require.NoError(t, err)
   617  
   618  		adminClient, err := factory.AdminClient(ctx)
   619  		require.NoError(t, err)
   620  
   621  		topic, ok := a.uriParams[0].(string)
   622  		require.True(t, ok)
   623  		require.NotEqual(t, "", topic)
   624  		err = AdjustOptions(ctx, adminClient, options, topic)
   625  		require.Nil(t, err)
   626  
   627  		encoderConfig := common.NewConfig(config.ProtocolOpen)
   628  		err = encoderConfig.Apply(sinkURI, &config.ReplicaConfig{
   629  			Sink: &config.SinkConfig{
   630  				KafkaConfig: &config.KafkaConfig{
   631  					LargeMessageHandle: config.NewDefaultLargeMessageHandleConfig(),
   632  				},
   633  			},
   634  		})
   635  		require.Nil(t, err)
   636  		encoderConfig.WithMaxMessageBytes(options.MaxMessageBytes)
   637  
   638  		err = encoderConfig.Validate()
   639  		require.Nil(t, err)
   640  
   641  		// producer's `MaxMessageBytes` = encoder's `MaxMessageBytes`.
   642  		require.Equal(t, encoderConfig.MaxMessageBytes, options.MaxMessageBytes)
   643  
   644  		adminClient.Close()
   645  	}
   646  }
   647  
   648  func TestMerge(t *testing.T) {
   649  	uri := "kafka://topic/prefix"
   650  	sinkURI, err := url.Parse(uri)
   651  	require.NoError(t, err)
   652  	replicaConfig := config.GetDefaultReplicaConfig()
   653  	replicaConfig.Sink.KafkaConfig = &config.KafkaConfig{
   654  		PartitionNum:              aws.Int32(12),
   655  		ReplicationFactor:         aws.Int16(5),
   656  		KafkaVersion:              aws.String("3.1.2"),
   657  		MaxMessageBytes:           aws.Int(1024 * 1024),
   658  		Compression:               aws.String("gzip"),
   659  		KafkaClientID:             aws.String("test-id"),
   660  		AutoCreateTopic:           aws.Bool(true),
   661  		DialTimeout:               aws.String("1m1s"),
   662  		WriteTimeout:              aws.String("2m1s"),
   663  		RequiredAcks:              aws.Int(1),
   664  		SASLUser:                  aws.String("abc"),
   665  		SASLPassword:              aws.String("123"),
   666  		SASLMechanism:             aws.String("plain"),
   667  		SASLGssAPIAuthType:        aws.String("keytab"),
   668  		SASLGssAPIKeytabPath:      aws.String("SASLGssAPIKeytabPath"),
   669  		SASLGssAPIServiceName:     aws.String("service"),
   670  		SASLGssAPIUser:            aws.String("user"),
   671  		SASLGssAPIPassword:        aws.String("pass"),
   672  		SASLGssAPIRealm:           aws.String("realm"),
   673  		SASLGssAPIDisablePafxfast: aws.Bool(true),
   674  		EnableTLS:                 aws.Bool(true),
   675  		CA:                        aws.String("ca.pem"),
   676  		Cert:                      aws.String("cert.pem"),
   677  		Key:                       aws.String("key.pem"),
   678  	}
   679  	c := NewOptions()
   680  	err = c.Apply(model.DefaultChangeFeedID("test"), sinkURI, replicaConfig)
   681  	require.NoError(t, err)
   682  	require.Equal(t, int32(12), c.PartitionNum)
   683  	require.Equal(t, int16(5), c.ReplicationFactor)
   684  	require.Equal(t, "3.1.2", c.Version)
   685  	require.Equal(t, 1024*1024, c.MaxMessageBytes)
   686  	require.Equal(t, "gzip", c.Compression)
   687  	require.Equal(t, "test-id", c.ClientID)
   688  	require.Equal(t, true, c.AutoCreate)
   689  	require.Equal(t, time.Minute+time.Second, c.DialTimeout)
   690  	require.Equal(t, 2*time.Minute+time.Second, c.WriteTimeout)
   691  	require.Equal(t, 1, int(c.RequiredAcks))
   692  	require.Equal(t, "abc", c.SASL.SASLUser)
   693  	require.Equal(t, "123", c.SASL.SASLPassword)
   694  	require.Equal(t, "plain", strings.ToLower(string(c.SASL.SASLMechanism)))
   695  	require.Equal(t, 2, int(c.SASL.GSSAPI.AuthType))
   696  	require.Equal(t, "SASLGssAPIKeytabPath", c.SASL.GSSAPI.KeyTabPath)
   697  	require.Equal(t, "service", c.SASL.GSSAPI.ServiceName)
   698  	require.Equal(t, "user", c.SASL.GSSAPI.Username)
   699  	require.Equal(t, "pass", c.SASL.GSSAPI.Password)
   700  	require.Equal(t, "realm", c.SASL.GSSAPI.Realm)
   701  	require.Equal(t, true, c.SASL.GSSAPI.DisablePAFXFAST)
   702  	require.Equal(t, true, c.EnableTLS)
   703  	require.Equal(t, "ca.pem", c.Credential.CAPath)
   704  	require.Equal(t, "cert.pem", c.Credential.CertPath)
   705  	require.Equal(t, "key.pem", c.Credential.KeyPath)
   706  
   707  	// test override
   708  	uri = "kafka://topic?partition-num=12" +
   709  		"&replication-factor=5" +
   710  		"&kafka-version=3.1.2" +
   711  		"&max-message-bytes=1048576" +
   712  		"&compression=gzip" +
   713  		"&kafka-client-id=test-id" +
   714  		"&auto-create-topic=true" +
   715  		"&dial-timeout=1m1s" +
   716  		"&write-timeout=2m1s" +
   717  		"&required-acks=1" +
   718  		"&sasl-user=abc" +
   719  		"&sasl-password=123" +
   720  		"&sasl-mechanism=plain" +
   721  		"&sasl-gssapi-auth-type=keytab" +
   722  		"&sasl-gssapi-keytab-path=SASLGssAPIKeytabPath" +
   723  		"&sasl-gssapi-service-name=service" +
   724  		"&sasl-gssapi-user=user" +
   725  		"&sasl-gssapi-password=pass" +
   726  		"&sasl-gssapi-realm=realm" +
   727  		"&sasl-gssapi-disable-pafxfast=true" +
   728  		"&enable-tls=true" +
   729  		"&ca=ca.pem" +
   730  		"&cert=cert.pem" +
   731  		"&key=key.pem"
   732  	sinkURI, err = url.Parse(uri)
   733  	require.NoError(t, err)
   734  	replicaConfig.Sink.KafkaConfig = &config.KafkaConfig{
   735  		PartitionNum:              aws.Int32(11),
   736  		ReplicationFactor:         aws.Int16(3),
   737  		KafkaVersion:              aws.String("3.2.2"),
   738  		MaxMessageBytes:           aws.Int(1023 * 1024),
   739  		Compression:               aws.String("none"),
   740  		KafkaClientID:             aws.String("test2-id"),
   741  		AutoCreateTopic:           aws.Bool(false),
   742  		DialTimeout:               aws.String("1m2s"),
   743  		WriteTimeout:              aws.String("2m3s"),
   744  		RequiredAcks:              aws.Int(-1),
   745  		SASLUser:                  aws.String("abcd"),
   746  		SASLPassword:              aws.String("1234"),
   747  		SASLMechanism:             aws.String("plain"),
   748  		SASLGssAPIAuthType:        aws.String("user"),
   749  		SASLGssAPIKeytabPath:      aws.String("path"),
   750  		SASLGssAPIServiceName:     aws.String("service2"),
   751  		SASLGssAPIUser:            aws.String("usera"),
   752  		SASLGssAPIPassword:        aws.String("pass2"),
   753  		SASLGssAPIRealm:           aws.String("realm2"),
   754  		SASLGssAPIDisablePafxfast: aws.Bool(false),
   755  		EnableTLS:                 aws.Bool(false),
   756  		CA:                        aws.String("ca2.pem"),
   757  		Cert:                      aws.String("cert2.pem"),
   758  		Key:                       aws.String("key2.pem"),
   759  	}
   760  	c = NewOptions()
   761  	err = c.Apply(model.DefaultChangeFeedID("test"), sinkURI, replicaConfig)
   762  	require.NoError(t, err)
   763  	require.Equal(t, int32(12), c.PartitionNum)
   764  	require.Equal(t, int16(5), c.ReplicationFactor)
   765  	require.Equal(t, "3.1.2", c.Version)
   766  	require.Equal(t, 1024*1024, c.MaxMessageBytes)
   767  	require.Equal(t, "gzip", c.Compression)
   768  	require.Equal(t, "test-id", c.ClientID)
   769  	require.Equal(t, true, c.AutoCreate)
   770  	require.Equal(t, time.Minute+time.Second, c.DialTimeout)
   771  	require.Equal(t, 2*time.Minute+time.Second, c.WriteTimeout)
   772  	require.Equal(t, 1, int(c.RequiredAcks))
   773  	require.Equal(t, "abc", c.SASL.SASLUser)
   774  	require.Equal(t, "123", c.SASL.SASLPassword)
   775  	require.Equal(t, "plain", strings.ToLower(string(c.SASL.SASLMechanism)))
   776  	require.Equal(t, 2, int(c.SASL.GSSAPI.AuthType))
   777  	require.Equal(t, "SASLGssAPIKeytabPath", c.SASL.GSSAPI.KeyTabPath)
   778  	require.Equal(t, "service", c.SASL.GSSAPI.ServiceName)
   779  	require.Equal(t, "user", c.SASL.GSSAPI.Username)
   780  	require.Equal(t, "pass", c.SASL.GSSAPI.Password)
   781  	require.Equal(t, "realm", c.SASL.GSSAPI.Realm)
   782  	require.Equal(t, true, c.SASL.GSSAPI.DisablePAFXFAST)
   783  	require.Equal(t, true, c.EnableTLS)
   784  	require.Equal(t, "ca.pem", c.Credential.CAPath)
   785  	require.Equal(t, "cert.pem", c.Credential.CertPath)
   786  	require.Equal(t, "key.pem", c.Credential.KeyPath)
   787  }