github.com/klaytn/klaytn@v1.10.2/datasync/chaindatafetcher/kafka/kafka_test.go (about) 1 // Copyright 2020 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 package kafka 18 19 import ( 20 "context" 21 "encoding/binary" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/rand" 26 "reflect" 27 "strconv" 28 "strings" 29 "sync" 30 "testing" 31 "time" 32 33 "github.com/stretchr/testify/assert" 34 35 "github.com/Shopify/sarama" 36 "github.com/klaytn/klaytn/common" 37 "github.com/stretchr/testify/suite" 38 ) 39 40 type KafkaSuite struct { 41 suite.Suite 42 conf *KafkaConfig 43 kfk *Kafka 44 consumer sarama.Consumer 45 topic string 46 } 47 48 // In order to test KafkaSuite, any available kafka broker must be connectable with "kafka:9094". 49 // If no kafka broker is available, the KafkaSuite tests are skipped. 50 func (s *KafkaSuite) SetupTest() { 51 s.conf = GetDefaultKafkaConfig() 52 s.conf.Brokers = []string{"kafka:9094"} 53 kfk, err := NewKafka(s.conf) 54 if err == sarama.ErrOutOfBrokers { 55 s.T().Log("Failed connecting to brokers", s.conf.Brokers) 56 s.T().Skip() 57 } 58 s.NoError(err) 59 s.kfk = kfk 60 61 consumer, err := sarama.NewConsumer(s.conf.Brokers, s.conf.SaramaConfig) 62 s.NoError(err) 63 s.consumer = consumer 64 s.topic = "test-topic" 65 } 66 67 func (s *KafkaSuite) TearDownTest() { 68 if s.kfk != nil { 69 s.kfk.Close() 70 } 71 } 72 73 func (s *KafkaSuite) TestKafka_split() { 74 segmentSizeBytes := 3 75 s.kfk.config.SegmentSizeBytes = segmentSizeBytes 76 77 // test with the size less than the segment size 78 bytes := common.MakeRandomBytes(segmentSizeBytes - 1) 79 parts, size := s.kfk.split(bytes) 80 s.Equal(bytes, parts[0]) 81 s.Equal(1, size) 82 83 // test with the given segment size 84 bytes = common.MakeRandomBytes(segmentSizeBytes) 85 parts, size = s.kfk.split(bytes) 86 s.Equal(bytes, parts[0]) 87 s.Equal(1, size) 88 89 // test with the size greater than the segment size 90 bytes = common.MakeRandomBytes(2*segmentSizeBytes + 2) 91 parts, size = s.kfk.split(bytes) 92 s.Equal(bytes[:segmentSizeBytes], parts[0]) 93 s.Equal(bytes[segmentSizeBytes:2*segmentSizeBytes], parts[1]) 94 s.Equal(bytes[2*segmentSizeBytes:], parts[2]) 95 s.Equal(3, size) 96 } 97 98 func (s *KafkaSuite) TestKafka_makeProducerV1Message() { 99 // make test data 100 data := common.MakeRandomBytes(100) 101 rand.Seed(time.Now().UnixNano()) 102 totalSegments := rand.Uint64() 103 idx := rand.Uint64() % totalSegments 104 105 // make a producer message with the random input 106 msg := s.kfk.makeProducerMessage(s.topic, "", data, idx, totalSegments) 107 108 // compare the data is correctly inserted 109 s.Equal(s.topic, msg.Topic) 110 s.Equal(sarama.ByteEncoder(data), msg.Value) 111 s.Equal(totalSegments, binary.BigEndian.Uint64(msg.Headers[MsgHeaderTotalSegments].Value)) 112 s.Equal(idx, binary.BigEndian.Uint64(msg.Headers[MsgHeaderSegmentIdx].Value)) 113 s.Equal(s.kfk.config.MsgVersion, string(msg.Headers[MsgHeaderVersion].Value)) 114 s.Equal(s.kfk.config.ProducerId, string(msg.Headers[MsgHeaderProducerId].Value)) 115 } 116 117 func (s *KafkaSuite) TestKafka_makeProducerMessage() { 118 // make test data 119 data := common.MakeRandomBytes(100) 120 rand.Seed(time.Now().UnixNano()) 121 totalSegments := rand.Uint64() 122 idx := rand.Uint64() % totalSegments 123 124 // make a producer message with the random input 125 msg := s.kfk.makeProducerMessage(s.topic, "", data, idx, totalSegments) 126 127 // compare the data is correctly inserted 128 s.Equal(s.topic, msg.Topic) 129 s.Equal(sarama.ByteEncoder(data), msg.Value) 130 s.Equal(totalSegments, binary.BigEndian.Uint64(msg.Headers[MsgHeaderTotalSegments].Value)) 131 s.Equal(idx, binary.BigEndian.Uint64(msg.Headers[MsgHeaderSegmentIdx].Value)) 132 } 133 134 func (s *KafkaSuite) TestKafka_setupTopic() { 135 topicName := "test-setup-topic" 136 137 // create a new topic 138 err := s.kfk.setupTopic(topicName) 139 s.NoError(err) 140 141 // try to create duplicated topic 142 err = s.kfk.setupTopic(topicName) 143 s.NoError(err) 144 } 145 146 func (s *KafkaSuite) TestKafka_setupTopicConcurrency() { 147 topicName := "test-setup-concurrency-topic" 148 wg := sync.WaitGroup{} 149 for i := 0; i < 10; i++ { 150 wg.Add(1) 151 go func() { 152 defer wg.Done() 153 kaf, err := NewKafka(s.conf) 154 s.NoError(err) 155 156 err = kaf.setupTopic(topicName) 157 s.NoError(err) 158 }() 159 } 160 wg.Wait() 161 } 162 163 func (s *KafkaSuite) TestKafka_CreateAndDeleteTopic() { 164 // no topic to be deleted 165 err := s.kfk.DeleteTopic(s.topic) 166 s.Error(err) 167 s.True(strings.Contains(err.Error(), sarama.ErrUnknownTopicOrPartition.Error())) 168 169 // created a topic successfully 170 err = s.kfk.CreateTopic(s.topic) 171 s.NoError(err) 172 173 // failed to create a duplicated topic 174 err = s.kfk.CreateTopic(s.topic) 175 s.Error(err) 176 s.True(strings.Contains(err.Error(), sarama.ErrTopicAlreadyExists.Error())) 177 178 // deleted a topic successfully 179 s.Nil(s.kfk.DeleteTopic(s.topic)) 180 181 topics, err := s.kfk.ListTopics() 182 if _, exist := topics[s.topic]; exist { 183 s.Fail("topic must not exist") 184 } 185 } 186 187 type kafkaData struct { 188 Number int 189 Data []byte `json:"data"` 190 } 191 192 func (d *kafkaData) Key() string { 193 return fmt.Sprintf("%v", d.Number) 194 } 195 196 func publishRandomData(t *testing.T, producer *Kafka, topic string, numTests, testBytesSize int) []*kafkaData { 197 var expected []*kafkaData 198 for i := 0; i < numTests; i++ { 199 testData := &kafkaData{i, common.MakeRandomBytes(testBytesSize)} 200 assert.NoError(t, producer.Publish(topic, testData)) 201 expected = append(expected, testData) 202 } 203 return expected 204 } 205 206 func (s *KafkaSuite) subscribeData(topic, groupId string, numTests int, handler func(message *sarama.ConsumerMessage) error) { 207 numCheckCh := make(chan struct{}, numTests) 208 209 // make a test consumer group 210 s.kfk.config.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest 211 consumer, err := NewConsumer(s.kfk.config, groupId) 212 s.NoError(err) 213 defer consumer.Close() 214 215 // add handler for the test event group 216 consumer.topics = append(consumer.topics, topic) 217 consumer.handlers[topic] = func(message *sarama.ConsumerMessage) error { 218 err := handler(message) 219 numCheckCh <- struct{}{} 220 return err 221 } 222 223 // subscribe the added topics 224 go func() { 225 err := consumer.Subscribe(context.Background()) 226 s.NoError(err) 227 }() 228 229 // wait for all data to be consumed 230 timeout := time.NewTimer(5 * time.Second) 231 for i := 0; i < numTests; i++ { 232 select { 233 case <-numCheckCh: 234 s.T().Logf("test count: %v, total tests: %v", i+1, numTests) 235 case <-timeout.C: 236 s.FailNow("timeout") 237 } 238 } 239 } 240 241 func (s *KafkaSuite) TestKafka_Publish() { 242 numTests := 10 243 testBytesSize := 100 244 245 s.kfk.CreateTopic(s.topic) 246 247 expected := publishRandomData(s.T(), s.kfk, s.topic, numTests, testBytesSize) 248 249 // consume from the first partition and the first item 250 partitionConsumer, err := s.consumer.ConsumePartition(s.topic, int32(0), int64(0)) 251 s.NoError(err) 252 253 var actual []*kafkaData 254 i := 0 255 for msg := range partitionConsumer.Messages() { 256 var dec *kafkaData 257 json.Unmarshal(msg.Value, &dec) 258 actual = append(actual, dec) 259 i++ 260 if i == numTests { 261 break 262 } 263 } 264 265 s.True(len(actual) == numTests) 266 for idx, v := range expected { 267 s.Equal(v, actual[idx]) 268 } 269 } 270 271 func (s *KafkaSuite) TestKafka_Subscribe() { 272 numTests := 10 273 testBytesSize := 100 274 275 topic := "test-subscribe" 276 s.kfk.CreateTopic(topic) 277 278 expected := publishRandomData(s.T(), s.kfk, topic, numTests, testBytesSize) 279 280 var actual []*kafkaData 281 s.subscribeData(topic, "test-group-id", numTests, func(message *sarama.ConsumerMessage) error { 282 var d *kafkaData 283 json.Unmarshal(message.Value, &d) 284 actual = append(actual, d) 285 return nil 286 }) 287 288 // compare the results with the published data 289 s.Equal(expected, actual) 290 } 291 292 func (s *KafkaSuite) TestKafka_PubSubWith2Partitions() { 293 numTests := 10 294 testBytesSize := 100 295 296 s.kfk.config.Partitions = 2 297 defer func() { s.kfk.config.Partitions = DefaultPartitions }() 298 299 topicPartition := "test-2-partition-topic" 300 s.kfk.CreateTopic(topicPartition) 301 302 // publish random data 303 expected := publishRandomData(s.T(), s.kfk, topicPartition, numTests, testBytesSize) 304 305 var actual []*kafkaData 306 s.subscribeData(topicPartition, "test-group-id", numTests, func(message *sarama.ConsumerMessage) error { 307 var d *kafkaData 308 json.Unmarshal(message.Value, &d) 309 actual = append(actual, d) 310 return nil 311 }) 312 313 // the number of partitions is not 1, so the order may be changed after subscription. 314 // compare the results with the published data 315 s.Equal(len(expected), len(actual)) 316 317 for _, e := range expected { 318 has := false 319 for _, a := range actual { 320 if reflect.DeepEqual(e, a) { 321 has = true 322 } 323 } 324 if !has { 325 s.Fail("the expected data is not contained in the actual data", "expected", e) 326 } 327 } 328 } 329 330 func (s *KafkaSuite) TestKafka_PubSubWith2DifferentGroups() { 331 numTests := 10 332 testBytesSize := 100 333 334 topic := "test-different-groups" 335 s.kfk.CreateTopic(topic) 336 337 // publish random data 338 expected := publishRandomData(s.T(), s.kfk, topic, numTests, testBytesSize) 339 340 var actual []*kafkaData 341 s.subscribeData(topic, "test-group-id-1", numTests, func(message *sarama.ConsumerMessage) error { 342 var d *kafkaData 343 json.Unmarshal(message.Value, &d) 344 actual = append(actual, d) 345 return nil 346 }) 347 348 var actual2 []*kafkaData 349 s.subscribeData(topic, "test-group-id-2", numTests, func(message *sarama.ConsumerMessage) error { 350 var d *kafkaData 351 json.Unmarshal(message.Value, &d) 352 actual2 = append(actual2, d) 353 return nil 354 }) 355 356 // the number of partitions is not 1, so the order may be changed after subscription. 357 // compare the results with the published data 358 s.Equal(expected, actual) 359 s.Equal(expected, actual2) 360 } 361 362 func (s *KafkaSuite) TestKafka_PubSubWithV1Segments() { 363 numProducers := 3 364 numTests := 10 365 testBytesSize := 31 366 segmentSize := 3 367 368 // make multi producers 369 var producers []*Kafka 370 for i := 0; i < numProducers; i++ { 371 config := GetDefaultKafkaConfig() 372 config.Brokers = s.kfk.config.Brokers 373 config.SegmentSizeBytes = segmentSize 374 config.SaramaConfig.Producer.RequiredAcks = -1 375 kfk, err := NewKafka(config) 376 s.NoError(err) 377 producers = append(producers, kfk) 378 } 379 380 topic := "test-multi-producer-segments" 381 s.kfk.CreateTopic(topic) 382 383 var expected []*kafkaData 384 { // produce messages 385 wg := sync.WaitGroup{} 386 dataLock := sync.Mutex{} 387 for _, p := range producers { 388 wg.Add(1) 389 go func(producer *Kafka) { 390 data := publishRandomData(s.T(), producer, topic, numTests, testBytesSize) 391 dataLock.Lock() 392 expected = append(expected, data...) 393 dataLock.Unlock() 394 wg.Done() 395 }(p) 396 } 397 wg.Wait() 398 } 399 400 var actual []*kafkaData 401 s.subscribeData(topic, "test-multi-producers-consumer", numTests*numProducers, func(message *sarama.ConsumerMessage) error { 402 var d *kafkaData 403 json.Unmarshal(message.Value, &d) 404 actual = append(actual, d) 405 return nil 406 }) 407 408 for _, expectedData := range expected { 409 exist := false 410 for _, actualData := range actual { 411 if reflect.DeepEqual(actualData, expectedData) { 412 exist = true 413 break 414 } 415 } 416 assert.True(s.T(), exist) 417 } 418 } 419 420 func (s *KafkaSuite) TestKafka_PubSubWithSegments() { 421 numTests := 5 422 testBytesSize := 10 423 segmentSize := 3 424 425 s.kfk.config.SegmentSizeBytes = segmentSize 426 topic := "test-message-segments" 427 s.kfk.CreateTopic(topic) 428 429 // publish random data 430 expected := publishRandomData(s.T(), s.kfk, topic, numTests, testBytesSize) 431 432 var actual []*kafkaData 433 s.subscribeData(topic, "test-group-id", numTests, func(message *sarama.ConsumerMessage) error { 434 var d *kafkaData 435 json.Unmarshal(message.Value, &d) 436 actual = append(actual, d) 437 return nil 438 }) 439 s.Equal(expected, actual) 440 } 441 442 func (s *KafkaSuite) TestKafka_PubSubWithSegements_BufferOverflow() { 443 // create a topic 444 topic := "test-message-segments-buffer-overflow" 445 err := s.kfk.setupTopic(topic) 446 s.NoError(err) 447 448 // insert incomplete message segments 449 for i := 0; i < 3; i++ { 450 msg := s.kfk.makeProducerMessage(topic, "test-key-"+strconv.Itoa(i), common.MakeRandomBytes(10), 0, 2) 451 _, _, err = s.kfk.producer.SendMessage(msg) 452 s.NoError(err) 453 } 454 455 // setup consumer to handle errors 456 s.kfk.config.MaxMessageNumber = 1 // if buffer size is greater than 1, then it returns an error 457 s.kfk.config.SaramaConfig.Consumer.Return.Errors = true 458 s.kfk.config.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest 459 consumer, err := NewConsumer(s.kfk.config, "test-group-id") 460 s.NoError(err) 461 consumer.topics = append(consumer.topics, topic) 462 consumer.handlers[topic] = func(message *sarama.ConsumerMessage) error { return nil } 463 errCh := consumer.Errors() 464 465 go func() { 466 err = consumer.Subscribe(context.Background()) 467 s.NoError(err) 468 }() 469 470 // checkout the returned error is buffer overflow error 471 timeout := time.NewTimer(3 * time.Second) 472 select { 473 case <-timeout.C: 474 s.Fail("timeout") 475 case err := <-errCh: 476 s.True(strings.Contains(err.Error(), bufferOverflowErrorMsg)) 477 } 478 } 479 480 func (s *KafkaSuite) TestKafka_PubSubWithSegments_ErrCallBack() { 481 // create a topic 482 topic := "test-message-segments-error-callback" 483 err := s.kfk.setupTopic(topic) 484 s.NoError(err) 485 486 _ = publishRandomData(s.T(), s.kfk, topic, 1, 1) 487 488 // setup consumer to handle errors with callback method 489 s.kfk.config.SaramaConfig.Consumer.Return.Errors = true 490 s.kfk.config.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest 491 callbackErr := errors.New("callback error") 492 s.kfk.config.ErrCallback = func(string) error { return callbackErr } 493 494 // create a consumer structure 495 consumer, err := NewConsumer(s.kfk.config, "test-group-id") 496 s.NoError(err) 497 consumer.topics = append(consumer.topics, topic) 498 consumer.handlers[topic] = func(message *sarama.ConsumerMessage) error { return errors.New("test error") } 499 500 go func() { 501 err = consumer.Subscribe(context.Background()) 502 s.NoError(err) 503 }() 504 505 // checkout the returned error is callback error 506 timeout := time.NewTimer(3 * time.Second) 507 select { 508 case <-timeout.C: 509 s.Fail("timeout") 510 case err := <-consumer.Errors(): 511 s.Error(err) 512 s.True(strings.Contains(err.Error(), callbackErr.Error())) 513 } 514 } 515 516 func (s *KafkaSuite) TestKafka_PubSubWithSegments_MessageTimeout() { 517 // create a topic 518 topic := "test-message-segments-expiration" 519 err := s.kfk.setupTopic(topic) 520 s.NoError(err) 521 522 // produce incomplete message 523 msg := s.kfk.makeProducerMessage(topic, "test-key", common.MakeRandomBytes(10), 0, 2) 524 _, _, err = s.kfk.producer.SendMessage(msg) 525 s.NoError(err) 526 527 // setup consumer to handle errors with callback method 528 s.kfk.config.SaramaConfig.Consumer.Return.Errors = true 529 s.kfk.config.SaramaConfig.Consumer.Offsets.Initial = sarama.OffsetOldest 530 s.kfk.config.ExpirationTime = 300 * time.Millisecond 531 532 // create a consumer structure 533 consumer, err := NewConsumer(s.kfk.config, "test-group-id") 534 s.NoError(err) 535 consumer.topics = append(consumer.topics, topic) 536 consumer.handlers[topic] = func(message *sarama.ConsumerMessage) error { 537 // sleep for message expiration 538 time.Sleep(1 * time.Second) 539 return nil 540 } 541 542 go func() { 543 err = consumer.Subscribe(context.Background()) 544 s.NoError(err) 545 }() 546 547 // checkout the returned error is callback error 548 timeout := time.NewTimer(3 * time.Second) 549 select { 550 case <-timeout.C: 551 s.Fail("timeout") 552 case err := <-consumer.Errors(): 553 s.Error(err) 554 s.True(strings.Contains(err.Error(), msgExpiredErrorMsg)) 555 } 556 } 557 558 func (s *KafkaSuite) TestKafka_Consumer_AddTopicAndHandler() { 559 consumer, err := NewConsumer(s.kfk.config, "test-group-id") 560 s.NoError(err) 561 562 blockGroupHandler := func(msg *sarama.ConsumerMessage) error { return nil } 563 s.NoError(consumer.AddTopicAndHandler(EventBlockGroup, blockGroupHandler)) 564 traceGroupHandler := func(msg *sarama.ConsumerMessage) error { return nil } 565 s.NoError(consumer.AddTopicAndHandler(EventTraceGroup, traceGroupHandler)) 566 567 blockGroupTopic := s.kfk.config.GetTopicName(EventBlockGroup) 568 traceGroupTopic := s.kfk.config.GetTopicName(EventTraceGroup) 569 expectedTopics := []string{blockGroupTopic, traceGroupTopic} 570 s.Equal(expectedTopics, consumer.topics) 571 s.Equal(reflect.ValueOf(blockGroupHandler).Pointer(), reflect.ValueOf(consumer.handlers[blockGroupTopic]).Pointer()) 572 s.Equal(reflect.ValueOf(traceGroupHandler).Pointer(), reflect.ValueOf(consumer.handlers[traceGroupTopic]).Pointer()) 573 } 574 575 func (s *KafkaSuite) TestKafka_Consumer_AddTopicAndHandler_Error() { 576 consumer, err := NewConsumer(s.kfk.config, "test-group-id") 577 s.NoError(err) 578 579 err = consumer.AddTopicAndHandler("not-available-event", nil) 580 s.Error(err) 581 s.True(strings.Contains(err.Error(), eventNameErrorMsg)) 582 } 583 584 func TestKafkaSuite(t *testing.T) { 585 suite.Run(t, new(KafkaSuite)) 586 }