mirror of https://github.com/apache/kafka.git
KAFKA-19042 move ConsumerWithLegacyMessageFormatIntegrationTest to clients-integration-tests module (#19810)
CI / build (push) Waiting to run
Details
CI / build (push) Waiting to run
Details
This PR rewrites `ConsumerWithLegacyMessageFormatIntegrationTest.scala` in Java and moves it to the `clients-integration-tests module`. Reviewers: PoAn Yang <payang@apache.org>, Ken Huang <s7133700@gmail.com>, TengYao Chi <kitingiao@gmail.com>, Chia-Ping Tsai <chia7712@gmail.com>
This commit is contained in:
parent
d1f1e5c8fd
commit
a122ac9d51
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
package org.apache.kafka.clients.consumer;
|
||||
|
||||
import org.apache.kafka.clients.ClientsTestUtils;
|
||||
import org.apache.kafka.clients.admin.Admin;
|
||||
import org.apache.kafka.clients.admin.NewTopic;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.apache.kafka.common.compress.Compression;
|
||||
import org.apache.kafka.common.record.AbstractRecords;
|
||||
import org.apache.kafka.common.record.CompressionType;
|
||||
import org.apache.kafka.common.record.MemoryRecords;
|
||||
import org.apache.kafka.common.record.MemoryRecordsBuilder;
|
||||
import org.apache.kafka.common.record.RecordBatch;
|
||||
import org.apache.kafka.common.record.RecordVersion;
|
||||
import org.apache.kafka.common.record.SimpleRecord;
|
||||
import org.apache.kafka.common.record.TimestampType;
|
||||
import org.apache.kafka.common.test.ClusterInstance;
|
||||
import org.apache.kafka.common.test.api.ClusterTest;
|
||||
import org.apache.kafka.common.test.api.ClusterTestDefaults;
|
||||
import org.apache.kafka.storage.internals.log.UnifiedLog;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_PROTOCOL_CONFIG;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
||||
@ClusterTestDefaults(
|
||||
brokers = 3
|
||||
)
|
||||
public class ConsumerWithLegacyMessageFormatIntegrationTest {
|
||||
|
||||
private final ClusterInstance cluster;
|
||||
|
||||
private final String topic1 = "part-test-topic-1";
|
||||
private final String topic2 = "part-test-topic-2";
|
||||
private final String topic3 = "part-test-topic-3";
|
||||
|
||||
private final TopicPartition t1p0 = new TopicPartition(topic1, 0);
|
||||
private final TopicPartition t1p1 = new TopicPartition(topic1, 1);
|
||||
private final TopicPartition t2p0 = new TopicPartition(topic2, 0);
|
||||
private final TopicPartition t2p1 = new TopicPartition(topic2, 1);
|
||||
private final TopicPartition t3p0 = new TopicPartition(topic3, 0);
|
||||
private final TopicPartition t3p1 = new TopicPartition(topic3, 1);
|
||||
|
||||
public ConsumerWithLegacyMessageFormatIntegrationTest(ClusterInstance cluster) {
|
||||
this.cluster = cluster;
|
||||
}
|
||||
|
||||
private void appendLegacyRecords(int numRecords, TopicPartition tp, int brokerId, byte magicValue) {
|
||||
List<SimpleRecord> records = new ArrayList<>();
|
||||
for (int i = 0; i < numRecords; i++) {
|
||||
records.add(new SimpleRecord(i, ("key " + i).getBytes(), ("value " + i).getBytes()));
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(AbstractRecords.estimateSizeInBytes(magicValue,
|
||||
CompressionType.NONE, records));
|
||||
MemoryRecordsBuilder builder = MemoryRecords.builder(
|
||||
buffer,
|
||||
magicValue,
|
||||
Compression.of(CompressionType.NONE).build(),
|
||||
TimestampType.CREATE_TIME,
|
||||
0L,
|
||||
RecordBatch.NO_TIMESTAMP,
|
||||
RecordBatch.NO_PRODUCER_ID,
|
||||
RecordBatch.NO_PRODUCER_EPOCH,
|
||||
0,
|
||||
false,
|
||||
RecordBatch.NO_PARTITION_LEADER_EPOCH
|
||||
);
|
||||
|
||||
records.forEach(builder::append);
|
||||
|
||||
cluster.brokers().values().stream()
|
||||
.filter(b -> b.config().brokerId() == brokerId)
|
||||
.forEach(b -> {
|
||||
UnifiedLog unifiedLog = b.replicaManager().logManager().getLog(tp, false).get();
|
||||
unifiedLog.appendAsLeaderWithRecordVersion(builder.build(), 0, RecordVersion.lookup(magicValue));
|
||||
// Default isolation.level is read_uncommitted. It makes Partition#fetchOffsetForTimestamp to return UnifiedLog#highWatermark,
|
||||
// so increasing high watermark to make it return the correct offset.
|
||||
assertDoesNotThrow(() -> unifiedLog.maybeIncrementHighWatermark(unifiedLog.logEndOffsetMetadata()));
|
||||
});
|
||||
}
|
||||
|
||||
private void createTopicWithAssignment(String topic, Map<Integer, List<Integer>> assignment) throws InterruptedException {
|
||||
try (Admin admin = cluster.admin()) {
|
||||
NewTopic newTopic = new NewTopic(topic, assignment);
|
||||
admin.createTopics(List.of(newTopic));
|
||||
cluster.waitForTopic(topic, assignment.size());
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setupTopics() throws InterruptedException {
|
||||
cluster.createTopic(topic1, 2, (short) 1);
|
||||
createTopicWithAssignment(topic2, Map.of(0, List.of(0), 1, List.of(1)));
|
||||
createTopicWithAssignment(topic3, Map.of(0, List.of(0), 1, List.of(1)));
|
||||
|
||||
// v2 message format for topic1
|
||||
ClientsTestUtils.sendRecords(cluster, t1p0, 100, 0);
|
||||
ClientsTestUtils.sendRecords(cluster, t1p1, 100, 0);
|
||||
// v0 message format for topic2
|
||||
appendLegacyRecords(100, t2p0, 0, RecordBatch.MAGIC_VALUE_V0);
|
||||
appendLegacyRecords(100, t2p1, 1, RecordBatch.MAGIC_VALUE_V0);
|
||||
// v1 message format for topic3
|
||||
appendLegacyRecords(100, t3p0, 0, RecordBatch.MAGIC_VALUE_V1);
|
||||
appendLegacyRecords(100, t3p1, 1, RecordBatch.MAGIC_VALUE_V1);
|
||||
}
|
||||
|
||||
@ClusterTest
|
||||
public void testOffsetsForTimesWithClassicConsumer() {
|
||||
testOffsetsForTimes(GroupProtocol.CLASSIC);
|
||||
}
|
||||
|
||||
@ClusterTest
|
||||
public void testOffsetsForTimesWithAsyncConsumer() {
|
||||
testOffsetsForTimes(GroupProtocol.CONSUMER);
|
||||
}
|
||||
|
||||
public void testOffsetsForTimes(GroupProtocol groupProtocol) {
|
||||
try (Consumer<Object, Object> consumer = cluster.consumer(Map.of(
|
||||
GROUP_PROTOCOL_CONFIG, groupProtocol.name.toLowerCase(Locale.ROOT)))
|
||||
) {
|
||||
// Test negative target time
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
consumer.offsetsForTimes(Map.of(t1p0, -1L)));
|
||||
|
||||
Map<TopicPartition, Long> timestampsToSearch = Map.of(
|
||||
t1p0, 0L,
|
||||
t1p1, 20L,
|
||||
t2p0, 40L,
|
||||
t2p1, 60L,
|
||||
t3p0, 80L,
|
||||
t3p1, 100L
|
||||
);
|
||||
|
||||
Map<TopicPartition, OffsetAndTimestamp> timestampOffsets = consumer.offsetsForTimes(timestampsToSearch);
|
||||
|
||||
OffsetAndTimestamp timestampTopic1P0 = timestampOffsets.get(t1p0);
|
||||
assertEquals(0, timestampTopic1P0.offset());
|
||||
assertEquals(0, timestampTopic1P0.timestamp());
|
||||
assertEquals(Optional.of(0), timestampTopic1P0.leaderEpoch());
|
||||
|
||||
OffsetAndTimestamp timestampTopic1P1 = timestampOffsets.get(t1p1);
|
||||
assertEquals(20, timestampTopic1P1.offset());
|
||||
assertEquals(20, timestampTopic1P1.timestamp());
|
||||
assertEquals(Optional.of(0), timestampTopic1P1.leaderEpoch());
|
||||
|
||||
OffsetAndTimestamp timestampTopic2P0 = timestampOffsets.get(t2p0);
|
||||
assertNull(timestampTopic2P0, "v0 message format shouldn't have timestamp");
|
||||
|
||||
OffsetAndTimestamp timestampTopic2P1 = timestampOffsets.get(t2p1);
|
||||
assertNull(timestampTopic2P1);
|
||||
|
||||
OffsetAndTimestamp timestampTopic3P0 = timestampOffsets.get(t3p0);
|
||||
assertEquals(80, timestampTopic3P0.offset());
|
||||
assertEquals(80, timestampTopic3P0.timestamp());
|
||||
assertEquals(Optional.empty(), timestampTopic3P0.leaderEpoch());
|
||||
|
||||
assertNull(timestampOffsets.get(t3p1), "v1 message format doesn't have leader epoch");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ClusterTest
|
||||
public void testEarliestOrLatestOffsetsWithClassicConsumer() {
|
||||
testEarliestOrLatestOffsets(GroupProtocol.CLASSIC);
|
||||
}
|
||||
|
||||
@ClusterTest
|
||||
public void testEarliestOrLatestOffsetsWithAsyncConsumer() {
|
||||
testEarliestOrLatestOffsets(GroupProtocol.CONSUMER);
|
||||
}
|
||||
|
||||
public void testEarliestOrLatestOffsets(GroupProtocol groupProtocol) {
|
||||
Set<TopicPartition> partitions = Set.of(t1p0, t1p1, t2p0, t2p1, t3p0, t3p1);
|
||||
|
||||
try (Consumer<Object, Object> consumer = cluster.consumer(Map.of(
|
||||
GROUP_PROTOCOL_CONFIG, groupProtocol.name.toLowerCase(Locale.ROOT)))
|
||||
) {
|
||||
Map<TopicPartition, Long> earliests = consumer.beginningOffsets(partitions);
|
||||
assertEquals(0L, earliests.get(t1p0));
|
||||
assertEquals(0L, earliests.get(t1p1));
|
||||
assertEquals(0L, earliests.get(t2p0));
|
||||
assertEquals(0L, earliests.get(t2p1));
|
||||
assertEquals(0L, earliests.get(t3p0));
|
||||
assertEquals(0L, earliests.get(t3p1));
|
||||
|
||||
Map<TopicPartition, Long> latests = consumer.endOffsets(partitions);
|
||||
assertEquals(100L, latests.get(t1p0));
|
||||
assertEquals(100L, latests.get(t1p1));
|
||||
assertEquals(100L, latests.get(t2p0));
|
||||
assertEquals(100L, latests.get(t2p1));
|
||||
assertEquals(100L, latests.get(t3p0));
|
||||
assertEquals(100L, latests.get(t3p1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.
|
||||
*/
|
||||
package kafka.api
|
||||
|
||||
import kafka.utils.TestInfoUtils
|
||||
import org.apache.kafka.common.TopicPartition
|
||||
import org.apache.kafka.common.compress.Compression
|
||||
import org.apache.kafka.common.record.{AbstractRecords, CompressionType, MemoryRecords, RecordBatch, RecordVersion, SimpleRecord, TimestampType}
|
||||
import org.junit.jupiter.api.Assertions.{assertEquals, assertNull, assertThrows}
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util
|
||||
import java.util.Optional
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
class ConsumerWithLegacyMessageFormatIntegrationTest extends AbstractConsumerTest {
|
||||
|
||||
val topic1 = "part-test-topic-1"
|
||||
val topic2 = "part-test-topic-2"
|
||||
val topic3 = "part-test-topic-3"
|
||||
|
||||
val t1p0 = new TopicPartition(topic1, 0)
|
||||
val t1p1 = new TopicPartition(topic1, 1)
|
||||
val t2p0 = new TopicPartition(topic2, 0)
|
||||
val t2p1 = new TopicPartition(topic2, 1)
|
||||
val t3p0 = new TopicPartition(topic3, 0)
|
||||
val t3p1 = new TopicPartition(topic3, 1)
|
||||
|
||||
private def appendLegacyRecords(numRecords: Int, tp: TopicPartition, brokerId: Int, magicValue: Byte): Unit = {
|
||||
val records = (0 until numRecords).map { i =>
|
||||
new SimpleRecord(i, s"key $i".getBytes, s"value $i".getBytes)
|
||||
}
|
||||
val buffer = ByteBuffer.allocate(AbstractRecords.estimateSizeInBytes(magicValue, CompressionType.NONE, records.asJava))
|
||||
val builder = MemoryRecords.builder(buffer, magicValue, Compression.of(CompressionType.NONE).build,
|
||||
TimestampType.CREATE_TIME, 0L, RecordBatch.NO_TIMESTAMP, RecordBatch.NO_PRODUCER_ID, RecordBatch.NO_PRODUCER_EPOCH,
|
||||
0, false, RecordBatch.NO_PARTITION_LEADER_EPOCH)
|
||||
|
||||
records.foreach(builder.append)
|
||||
|
||||
brokers.filter(_.config.brokerId == brokerId).foreach(b => {
|
||||
val unifiedLog = b.replicaManager.logManager.getLog(tp).get
|
||||
unifiedLog.appendAsLeaderWithRecordVersion(builder.build(), 0, RecordVersion.lookup(magicValue)
|
||||
)
|
||||
// Default isolation.level is read_uncommitted. It makes Partition#fetchOffsetForTimestamp to return UnifiedLog#highWatermark,
|
||||
// so increasing high watermark to make it return the correct offset.
|
||||
unifiedLog.maybeIncrementHighWatermark(unifiedLog.logEndOffsetMetadata)
|
||||
})
|
||||
}
|
||||
|
||||
private def setupTopics(): Unit = {
|
||||
val producer = createProducer()
|
||||
createTopic(topic1, numPartitions = 2)
|
||||
createTopicWithAssignment(topic2, Map(0 -> List(0), 1 -> List(1)))
|
||||
createTopicWithAssignment(topic3, Map(0 -> List(0), 1 -> List(1)))
|
||||
|
||||
// v2 message format for topic1
|
||||
sendRecords(producer, numRecords = 100, t1p0, startingTimestamp = 0)
|
||||
sendRecords(producer, numRecords = 100, t1p1, startingTimestamp = 0)
|
||||
// v0 message format for topic2
|
||||
appendLegacyRecords(100, t2p0, 0, RecordBatch.MAGIC_VALUE_V0)
|
||||
appendLegacyRecords(100, t2p1, 1, RecordBatch.MAGIC_VALUE_V0)
|
||||
// v1 message format for topic3
|
||||
appendLegacyRecords(100, t3p0, 0, RecordBatch.MAGIC_VALUE_V1)
|
||||
appendLegacyRecords(100, t3p1, 1, RecordBatch.MAGIC_VALUE_V1)
|
||||
|
||||
producer.close()
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = TestInfoUtils.TestWithParameterizedGroupProtocolNames)
|
||||
@MethodSource(Array("getTestGroupProtocolParametersAll"))
|
||||
def testOffsetsForTimes(groupProtocol: String): Unit = {
|
||||
setupTopics()
|
||||
val consumer = createConsumer()
|
||||
|
||||
// Test negative target time
|
||||
assertThrows(classOf[IllegalArgumentException],
|
||||
() => consumer.offsetsForTimes(util.Map.of(t1p0, -1)))
|
||||
|
||||
val timestampsToSearch = util.Map.of[TopicPartition, java.lang.Long](
|
||||
t1p0, 0L,
|
||||
t1p1, 20L,
|
||||
t2p0, 40L,
|
||||
t2p1, 60L,
|
||||
t3p0, 80L,
|
||||
t3p1, 100L
|
||||
)
|
||||
|
||||
val timestampOffsets = consumer.offsetsForTimes(timestampsToSearch)
|
||||
|
||||
val timestampTopic1P0 = timestampOffsets.get(t1p0)
|
||||
assertEquals(0, timestampTopic1P0.offset)
|
||||
assertEquals(0, timestampTopic1P0.timestamp)
|
||||
assertEquals(Optional.of(0), timestampTopic1P0.leaderEpoch)
|
||||
|
||||
val timestampTopic1P1 = timestampOffsets.get(t1p1)
|
||||
assertEquals(20, timestampTopic1P1.offset)
|
||||
assertEquals(20, timestampTopic1P1.timestamp)
|
||||
assertEquals(Optional.of(0), timestampTopic1P1.leaderEpoch)
|
||||
|
||||
// v0 message format doesn't have timestamp
|
||||
val timestampTopic2P0 = timestampOffsets.get(t2p0)
|
||||
assertNull(timestampTopic2P0)
|
||||
|
||||
val timestampTopic2P1 = timestampOffsets.get(t2p1)
|
||||
assertNull(timestampTopic2P1)
|
||||
|
||||
// v1 message format doesn't have leader epoch
|
||||
val timestampTopic3P0 = timestampOffsets.get(t3p0)
|
||||
assertEquals(80, timestampTopic3P0.offset)
|
||||
assertEquals(80, timestampTopic3P0.timestamp)
|
||||
assertEquals(Optional.empty, timestampTopic3P0.leaderEpoch)
|
||||
|
||||
assertNull(timestampOffsets.get(t3p1))
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = TestInfoUtils.TestWithParameterizedGroupProtocolNames)
|
||||
@MethodSource(Array("getTestGroupProtocolParametersAll"))
|
||||
def testEarliestOrLatestOffsets(groupProtocol: String): Unit = {
|
||||
setupTopics()
|
||||
|
||||
val partitions = util.Set.of(t1p0, t1p1, t2p0, t2p1, t3p0, t3p1)
|
||||
val consumer = createConsumer()
|
||||
|
||||
val earliests = consumer.beginningOffsets(partitions)
|
||||
assertEquals(0L, earliests.get(t1p0))
|
||||
assertEquals(0L, earliests.get(t1p1))
|
||||
assertEquals(0L, earliests.get(t2p0))
|
||||
assertEquals(0L, earliests.get(t2p1))
|
||||
assertEquals(0L, earliests.get(t3p0))
|
||||
assertEquals(0L, earliests.get(t3p1))
|
||||
|
||||
val latests = consumer.endOffsets(partitions)
|
||||
assertEquals(100L, latests.get(t1p0))
|
||||
assertEquals(100L, latests.get(t1p1))
|
||||
assertEquals(100L, latests.get(t2p0))
|
||||
assertEquals(100L, latests.get(t2p1))
|
||||
assertEquals(100L, latests.get(t3p0))
|
||||
assertEquals(100L, latests.get(t3p1))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue