Skip to content
33 changes: 17 additions & 16 deletions downstreamadapter/sink/kafka/sink.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,24 @@ func Verify(ctx context.Context, changefeedID commonType.ChangeFeedID, uri *url.
if err != nil {
return errors.Trace(err)
}
if _, exists := topics[topic]; exists {
return nil
}

topicConfig := options.DeriveTopicConfig()
if !topicConfig.AutoCreate {
return errors.ErrKafkaInvalidConfig.GenWithStack("`auto-create-topic` is false, and %s not found", topic)
}
if _, exists := topics[topic]; !exists {
topicConfig := options.DeriveTopicConfig()
if !topicConfig.AutoCreate {
return errors.ErrKafkaInvalidConfig.GenWithStack("`auto-create-topic` is false, and %s not found", topic)
}
if err = topicConfig.ValidateReplicationFactor(adminClient); err != nil {
return err
}

// the topic is not created, only validate.
err = adminClient.CreateTopic(&kafka.TopicDetail{
Name: topic,
NumPartitions: topicConfig.PartitionNum,
ReplicationFactor: topicConfig.ReplicationFactor,
}, true)
if err != nil {
return errors.WrapError(errors.ErrKafkaCreateTopic, err)
// the topic is not created, only validate.
err = adminClient.CreateTopic(&kafka.TopicDetail{
Name: topic,
NumPartitions: topicConfig.PartitionNum,
ReplicationFactor: topicConfig.ReplicationFactor,
}, true)
if err != nil {
return errors.WrapError(errors.ErrKafkaCreateTopic, err)
}
}

_, err = codec.NewEventEncoder(ctx, encoderConfig, claimCheck)
Expand Down
45 changes: 36 additions & 9 deletions downstreamadapter/sink/kafka/sink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ package kafka
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

"github.com/IBM/sarama"
"github.com/golang/mock/gomock"
"github.com/pingcap/ticdc/downstreamadapter/sink/columnselector"
"github.com/pingcap/ticdc/downstreamadapter/sink/eventrouter"
Expand All @@ -37,17 +40,41 @@ import (

const kafkaSinkTestTopic = "mock_topic"

func TestVerifyValidatesEncoderConfigBeforeKafkaConnection(t *testing.T) {
openProtocol := config.ProtocolOpen.String()
sinkConfig := &config.SinkConfig{Protocol: &openProtocol}
sinkURI, err := url.Parse("kafka://127.0.0.1:1/" + kafkaSinkTestTopic + "?max-batch-size=0")
func TestVerifyInvalidConfig(t *testing.T) {
broker := sarama.NewMockBroker(t, 1)
defer broker.Close()
broker.SetHandlerByMap(map[string]sarama.MockResponse{
"ApiVersionsRequest": sarama.NewMockApiVersionsResponse(t).SetApiKeys(
[]sarama.ApiVersionsResponseKey{
{ApiKey: 0},
{ApiKey: 1},
{ApiKey: 2},
{ApiKey: 3, MaxVersion: 9},
}),
"MetadataRequest": sarama.NewMockMetadataResponse(t).
SetController(broker.BrokerID()).
SetBroker(broker.Addr(), broker.BrokerID()).
SetLeader(kafkaSinkTestTopic, 0, broker.BrokerID()),
"DescribeConfigsRequest": sarama.NewMockDescribeConfigsResponse(t),
})

schemaRegistry := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "invalid response", http.StatusInternalServerError)
}))
defer schemaRegistry.Close()

avroProtocol := config.ProtocolAvro.String()
sinkConfig := &config.SinkConfig{
Protocol: &avroProtocol,
SchemaRegistry: &schemaRegistry.URL,
}
sinkURI, err := url.Parse("kafka://" + broker.Addr() + "/" + kafkaSinkTestTopic +
"?required-acks=1&kafka-version=2.4.0")
require.NoError(t, err)

changefeedID := common.NewChangefeedID4Test("test", "verify-existing-topic")
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
err = Verify(ctx, changefeedID, sinkURI, sinkConfig)
require.ErrorContains(t, err, "invalid max-batch-size 0")
changefeedID := common.NewChangefeedID4Test("test", "verify-invalid-config")
err = Verify(context.Background(), changefeedID, sinkURI, sinkConfig)
require.ErrorContains(t, err, "ErrAvroSchemaAPIError")
}

func newKafkaSinkForTestWithProducers(ctx context.Context,
Expand Down
9 changes: 5 additions & 4 deletions downstreamadapter/sink/topicmanager/kafka_topic_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ package topicmanager

import (
"context"
"fmt"
"sync"
"time"

Expand Down Expand Up @@ -239,9 +238,11 @@ func (m *kafkaTopicManager) createTopic(
topicName string,
) (int32, error) {
if !m.cfg.AutoCreate {
return 0, errors.ErrKafkaInvalidConfig.GenWithStack(
fmt.Sprintf("`auto-create-topic` is false, "+
"and %s not found", topicName))
return 0, errors.ErrKafkaInvalidConfig.GenWithStack("`auto-create-topic` is false, and %s not found", topicName)
}

if err := m.cfg.ValidateReplicationFactor(m.admin); err != nil {
return 0, err
}

start := time.Now()
Expand Down
42 changes: 41 additions & 1 deletion downstreamadapter/sink/topicmanager/kafka_topic_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestCreateTopic(t *testing.T) {
AutoCreate: true,
PartitionNum: 2,
ReplicationFactor: 1,
RequiredAcks: kafka.WaitForAll,
}

changefeedID := common.NewChangefeedID4Test("test", "test")
Expand Down Expand Up @@ -137,6 +138,7 @@ func TestCreateTopic(t *testing.T) {
require.NoError(t, err)
require.Equal(t, int32(2), partitionNum)

cfg.RequiredAcks = kafka.WaitForLocal
partitionNum, err = manager.CreateTopicAndWaitUntilVisible(ctx, "new-topic")
require.NoError(t, err)
require.Equal(t, int32(2), partitionNum)
Expand All @@ -151,7 +153,12 @@ func TestCreateTopic(t *testing.T) {
require.Equal(t, int32(2), partitionsNum)

// Try to create a topic without auto create.
cfg.AutoCreate = false
cfg = &kafka.AutoCreateTopicConfig{
AutoCreate: false,
PartitionNum: 2,
ReplicationFactor: 1,
RequiredAcks: kafka.WaitForAll,
}
manager = newKafkaTopicManager(ctx, "new-topic2", changefeedID, adminClient, cfg)
defer manager.Close()
_, err = manager.CreateTopicAndWaitUntilVisible(ctx, "new-topic2")
Expand Down Expand Up @@ -182,6 +189,39 @@ func TestCreateTopic(t *testing.T) {
require.False(t, gotFailedTopicValidateOnly)
}

func TestCreateTopicValidatesReplicationFactor(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
adminClient := kafka.NewMockClusterAdminClient(ctrl)
topic := "new-topic"
gomock.InOrder(
adminClient.EXPECT().GetTopicsMeta([]string{topic}, true).
Return(map[string]kafka.TopicDetail{}, nil),
adminClient.EXPECT().GetTopicsMeta([]string{topic}, false).
Return(map[string]kafka.TopicDetail{}, nil),
adminClient.EXPECT().GetBrokerConfig(kafka.MinInsyncReplicasConfigName).
Return("2", nil),
)

manager := newKafkaTopicManager(
context.Background(),
topic,
common.NewChangefeedID4Test("test", "test"),
adminClient,
&kafka.AutoCreateTopicConfig{
AutoCreate: true,
PartitionNum: 2,
ReplicationFactor: 1,
RequiredAcks: kafka.WaitForAll,
},
)
defer manager.Close()

_, err := manager.CreateTopicAndWaitUntilVisible(context.Background(), topic)
require.ErrorContains(t, err, "`replication-factor` 1 is smaller than the `min.insync.replicas` 2 of broker")
}

func TestCreateTopicWaitsUntilVisible(t *testing.T) {
t.Parallel()

Expand Down
Loading
Loading