|
|
|
package kafka
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copyright 2016 Confluent Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
#include "select_rdkafka.h"
|
|
|
|
*/
|
|
|
|
import "C"
|
|
|
|
|
|
|
|
var testconf struct {
|
|
|
|
Brokers string
|
|
|
|
Topic string
|
|
|
|
GroupID string
|
|
|
|
PerfMsgCount int
|
|
|
|
PerfMsgSize int
|
|
|
|
Config []string
|
|
|
|
conf ConfigMap
|
|
|
|
}
|
|
|
|
|
|
|
|
// testconf_read reads the test suite config file testconf.json which must
|
|
|
|
// contain at least Brokers and Topic string properties.
|
|
|
|
// Returns true if the testconf was found and usable, false if no such file, or panics
|
|
|
|
// if the file format is wrong.
|
|
|
|
func testconfRead() bool {
|
|
|
|
cf, err := os.Open("testconf.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%% testconf.json not found - ignoring test\n")
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default values
|
|
|
|
testconf.PerfMsgCount = 2000000
|
|
|
|
testconf.PerfMsgSize = 100
|
|
|
|
testconf.GroupID = "testgroup"
|
|
|
|
|
|
|
|
jp := json.NewDecoder(cf)
|
|
|
|
err = jp.Decode(&testconf)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("Failed to parse testconf: %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
cf.Close()
|
|
|
|
|
|
|
|
if testconf.Brokers[0] == '$' {
|
|
|
|
// Read broker list from environment variable
|
|
|
|
testconf.Brokers = os.Getenv(testconf.Brokers[1:])
|
|
|
|
}
|
|
|
|
|
|
|
|
if testconf.Brokers == "" || testconf.Topic == "" {
|
|
|
|
panic("Missing Brokers or Topic in testconf.json")
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// update existing ConfigMap with key=value pairs from testconf.SerializerConfig
|
|
|
|
func (cm *ConfigMap) updateFromTestconf() error {
|
|
|
|
if testconf.Config == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Translate "key=value" pairs in SerializerConfig to ConfigMap
|
|
|
|
for _, s := range testconf.Config {
|
|
|
|
err := cm.Set(s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the number of messages available in all partitions of a topic.
|
|
|
|
// WARNING: This uses watermark offsets so it will be incorrect for compacted topics.
|
|
|
|
func getMessageCountInTopic(topic string) (int, error) {
|
|
|
|
|
|
|
|
// Create consumer
|
|
|
|
config := &ConfigMap{"bootstrap.servers": testconf.Brokers,
|
|
|
|
"group.id": testconf.GroupID}
|
|
|
|
config.updateFromTestconf()
|
|
|
|
|
|
|
|
c, err := NewConsumer(config)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
defer c.Close()
|
|
|
|
|
|
|
|
// get metadata for the topic to find out number of partitions
|
|
|
|
|
|
|
|
metadata, err := c.GetMetadata(&topic, false, 5*1000)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
t, ok := metadata.Topics[topic]
|
|
|
|
if !ok {
|
|
|
|
return 0, newError(C.RD_KAFKA_RESP_ERR__UNKNOWN_TOPIC)
|
|
|
|
}
|
|
|
|
|
|
|
|
cnt := 0
|
|
|
|
for _, p := range t.Partitions {
|
|
|
|
low, high, err := c.QueryWatermarkOffsets(topic, p.ID, 5*1000)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
cnt += int(high - low)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cnt, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getBrokerList returns a list of brokers (ids) in the cluster
|
|
|
|
func getBrokerList(H Handle) (brokers []int32, err error) {
|
|
|
|
md, err := getMetadata(H, nil, true, 15*1000)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
brokers = make([]int32, len(md.Brokers))
|
|
|
|
for i, mdBroker := range md.Brokers {
|
|
|
|
brokers[i] = mdBroker.ID
|
|
|
|
}
|
|
|
|
|
|
|
|
return brokers, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// waitTopicInMetadata waits for the given topic to show up in metadata
|
|
|
|
func waitTopicInMetadata(H Handle, topic string, timeoutMs int) error {
|
|
|
|
d, _ := time.ParseDuration(fmt.Sprintf("%dms", timeoutMs))
|
|
|
|
tEnd := time.Now().Add(d)
|
|
|
|
|
|
|
|
for {
|
|
|
|
remain := tEnd.Sub(time.Now()).Seconds()
|
|
|
|
if remain < 0.0 {
|
|
|
|
return newErrorFromString(ErrTimedOut,
|
|
|
|
fmt.Sprintf("Timed out waiting for topic %s to appear in metadata", topic))
|
|
|
|
}
|
|
|
|
|
|
|
|
md, err := getMetadata(H, nil, true, int(remain*1000))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, t := range md.Topics {
|
|
|
|
if t.Topic != topic {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if t.Error.Code() != ErrNoError || len(t.Partitions) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Proper topic found in metadata
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(500 * 1000) // 500ms
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func createAdminClient(t *testing.T) (a *AdminClient) {
|
|
|
|
numver, strver := LibraryVersion()
|
|
|
|
if numver < 0x000b0500 {
|
|
|
|
t.Skipf("Requires librdkafka >=0.11.5 (currently on %s, 0x%x)", strver, numver)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !testconfRead() {
|
|
|
|
t.Skipf("Missing testconf.json")
|
|
|
|
}
|
|
|
|
|
|
|
|
conf := ConfigMap{"bootstrap.servers": testconf.Brokers}
|
|
|
|
conf.updateFromTestconf()
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create producer and produce a couple of messages with and without
|
|
|
|
* headers.
|
|
|
|
*/
|
|
|
|
a, err := NewAdminClient(&conf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("NewAdminClient: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
func createTestTopic(t *testing.T, suffix string, numPartitions int, replicationFactor int) string {
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
|
|
|
|
|
|
topic := fmt.Sprintf("%s-%s-%d", testconf.Topic, suffix, rand.Intn(100000))
|
|
|
|
|
|
|
|
a := createAdminClient(t)
|
|
|
|
defer a.Close()
|
|
|
|
|
|
|
|
newTopics := []TopicSpecification{
|
|
|
|
{
|
|
|
|
Topic: topic,
|
|
|
|
NumPartitions: numPartitions,
|
|
|
|
ReplicationFactor: replicationFactor,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
maxDuration, err := time.ParseDuration("30s")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("%s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), maxDuration)
|
|
|
|
defer cancel()
|
|
|
|
result, err := a.CreateTopics(ctx, newTopics, nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CreateTopics() failed: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, res := range result {
|
|
|
|
if res.Error.Code() != ErrNoError {
|
|
|
|
t.Errorf("Failed to create topic %s: %s\n",
|
|
|
|
res.Topic, res.Error)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return topic
|
|
|
|
}
|