|
@@ -1,26 +1,25 @@
|
|
package com.provectus.kafka.ui.service;
|
|
package com.provectus.kafka.ui.service;
|
|
|
|
|
|
-import static com.provectus.kafka.ui.model.SeekTypeDTO.BEGINNING;
|
|
|
|
-import static com.provectus.kafka.ui.model.SeekTypeDTO.LATEST;
|
|
|
|
-import static com.provectus.kafka.ui.model.SeekTypeDTO.OFFSET;
|
|
|
|
-import static com.provectus.kafka.ui.model.SeekTypeDTO.TIMESTAMP;
|
|
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.EARLIEST;
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.FROM_OFFSET;
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.FROM_TIMESTAMP;
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.LATEST;
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.TO_OFFSET;
|
|
|
|
+import static com.provectus.kafka.ui.model.PollingModeDTO.TO_TIMESTAMP;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
|
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
|
import com.provectus.kafka.ui.AbstractIntegrationTest;
|
|
-import com.provectus.kafka.ui.emitter.BackwardEmitter;
|
|
|
|
-import com.provectus.kafka.ui.emitter.EnhancedConsumer;
|
|
|
|
-import com.provectus.kafka.ui.emitter.ForwardEmitter;
|
|
|
|
|
|
+import com.provectus.kafka.ui.emitter.BackwardRecordEmitter;
|
|
|
|
+import com.provectus.kafka.ui.emitter.ForwardRecordEmitter;
|
|
import com.provectus.kafka.ui.emitter.PollingSettings;
|
|
import com.provectus.kafka.ui.emitter.PollingSettings;
|
|
-import com.provectus.kafka.ui.emitter.PollingThrottler;
|
|
|
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
|
import com.provectus.kafka.ui.model.ConsumerPosition;
|
|
-import com.provectus.kafka.ui.model.TopicMessageDTO;
|
|
|
|
|
|
+import com.provectus.kafka.ui.model.ConsumerPosition.Offsets;
|
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
|
import com.provectus.kafka.ui.model.TopicMessageEventDTO;
|
|
import com.provectus.kafka.ui.producer.KafkaTestProducer;
|
|
import com.provectus.kafka.ui.producer.KafkaTestProducer;
|
|
import com.provectus.kafka.ui.serde.api.Serde;
|
|
import com.provectus.kafka.ui.serde.api.Serde;
|
|
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
import com.provectus.kafka.ui.serdes.ConsumerRecordDeserializer;
|
|
import com.provectus.kafka.ui.serdes.PropertyResolverImpl;
|
|
import com.provectus.kafka.ui.serdes.PropertyResolverImpl;
|
|
import com.provectus.kafka.ui.serdes.builtin.StringSerde;
|
|
import com.provectus.kafka.ui.serdes.builtin.StringSerde;
|
|
-import com.provectus.kafka.ui.util.ApplicationMetrics;
|
|
|
|
import java.io.Serializable;
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
@@ -31,15 +30,17 @@ import java.util.UUID;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.function.Function;
|
|
-import java.util.function.Predicate;
|
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
import lombok.Value;
|
|
import lombok.Value;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.apache.kafka.clients.admin.NewTopic;
|
|
import org.apache.kafka.clients.admin.NewTopic;
|
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
|
|
|
+import org.apache.kafka.clients.consumer.KafkaConsumer;
|
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
|
import org.apache.kafka.common.TopicPartition;
|
|
import org.apache.kafka.common.TopicPartition;
|
|
import org.apache.kafka.common.header.internals.RecordHeader;
|
|
import org.apache.kafka.common.header.internals.RecordHeader;
|
|
|
|
+import org.apache.kafka.common.serialization.BytesDeserializer;
|
|
|
|
+import org.apache.kafka.common.utils.Bytes;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.AfterAll;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.Test;
|
|
@@ -57,16 +58,16 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
static final String EMPTY_TOPIC = TOPIC + "_empty";
|
|
static final String EMPTY_TOPIC = TOPIC + "_empty";
|
|
static final List<Record> SENT_RECORDS = new ArrayList<>();
|
|
static final List<Record> SENT_RECORDS = new ArrayList<>();
|
|
static final ConsumerRecordDeserializer RECORD_DESERIALIZER = createRecordsDeserializer();
|
|
static final ConsumerRecordDeserializer RECORD_DESERIALIZER = createRecordsDeserializer();
|
|
- static final Predicate<TopicMessageDTO> NOOP_FILTER = m -> true;
|
|
|
|
|
|
|
|
@BeforeAll
|
|
@BeforeAll
|
|
static void generateMsgs() throws Exception {
|
|
static void generateMsgs() throws Exception {
|
|
createTopic(new NewTopic(TOPIC, PARTITIONS, (short) 1));
|
|
createTopic(new NewTopic(TOPIC, PARTITIONS, (short) 1));
|
|
createTopic(new NewTopic(EMPTY_TOPIC, PARTITIONS, (short) 1));
|
|
createTopic(new NewTopic(EMPTY_TOPIC, PARTITIONS, (short) 1));
|
|
|
|
+ long startTs = System.currentTimeMillis();
|
|
try (var producer = KafkaTestProducer.forKafka(kafka)) {
|
|
try (var producer = KafkaTestProducer.forKafka(kafka)) {
|
|
for (int partition = 0; partition < PARTITIONS; partition++) {
|
|
for (int partition = 0; partition < PARTITIONS; partition++) {
|
|
for (int i = 0; i < MSGS_PER_PARTITION; i++) {
|
|
for (int i = 0; i < MSGS_PER_PARTITION; i++) {
|
|
- long ts = System.currentTimeMillis() + i;
|
|
|
|
|
|
+ long ts = (startTs += 100);
|
|
var value = "msg_" + partition + "_" + i;
|
|
var value = "msg_" + partition + "_" + i;
|
|
var metadata = producer.send(
|
|
var metadata = producer.send(
|
|
new ProducerRecord<>(
|
|
new ProducerRecord<>(
|
|
@@ -93,7 +94,6 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
static void cleanup() {
|
|
static void cleanup() {
|
|
deleteTopic(TOPIC);
|
|
deleteTopic(TOPIC);
|
|
deleteTopic(EMPTY_TOPIC);
|
|
deleteTopic(EMPTY_TOPIC);
|
|
- SENT_RECORDS.clear();
|
|
|
|
}
|
|
}
|
|
|
|
|
|
private static ConsumerRecordDeserializer createRecordsDeserializer() {
|
|
private static ConsumerRecordDeserializer createRecordsDeserializer() {
|
|
@@ -106,28 +106,24 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
s.deserializer(null, Serde.Target.VALUE),
|
|
s.deserializer(null, Serde.Target.VALUE),
|
|
StringSerde.name(),
|
|
StringSerde.name(),
|
|
s.deserializer(null, Serde.Target.KEY),
|
|
s.deserializer(null, Serde.Target.KEY),
|
|
- s.deserializer(null, Serde.Target.VALUE),
|
|
|
|
- msg -> msg
|
|
|
|
|
|
+ s.deserializer(null, Serde.Target.VALUE)
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
@Test
|
|
@Test
|
|
void pollNothingOnEmptyTopic() {
|
|
void pollNothingOnEmptyTopic() {
|
|
- var forwardEmitter = new ForwardEmitter(
|
|
|
|
|
|
+ var forwardEmitter = new ForwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
|
|
|
- 100,
|
|
|
|
|
|
+ new ConsumerPosition(EARLIEST, EMPTY_TOPIC, List.of(), null, null),
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(BEGINNING, EMPTY_TOPIC, null),
|
|
|
|
|
|
+ new ConsumerPosition(EARLIEST, EMPTY_TOPIC, List.of(), null, null),
|
|
100,
|
|
100,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
@@ -146,21 +142,18 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
|
|
|
|
@Test
|
|
@Test
|
|
void pollFullTopicFromBeginning() {
|
|
void pollFullTopicFromBeginning() {
|
|
- var forwardEmitter = new ForwardEmitter(
|
|
|
|
|
|
+ var forwardEmitter = new ForwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(BEGINNING, TOPIC, null),
|
|
|
|
- PARTITIONS * MSGS_PER_PARTITION,
|
|
|
|
|
|
+ new ConsumerPosition(EARLIEST, TOPIC, List.of(), null, null),
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(LATEST, TOPIC, null),
|
|
|
|
|
|
+ new ConsumerPosition(LATEST, TOPIC, List.of(), null, null),
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
@@ -178,21 +171,20 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
targetOffsets.put(new TopicPartition(TOPIC, i), offset);
|
|
targetOffsets.put(new TopicPartition(TOPIC, i), offset);
|
|
}
|
|
}
|
|
|
|
|
|
- var forwardEmitter = new ForwardEmitter(
|
|
|
|
|
|
+ var forwardEmitter = new ForwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
|
|
|
- PARTITIONS * MSGS_PER_PARTITION,
|
|
|
|
|
|
+ new ConsumerPosition(FROM_OFFSET, TOPIC, List.copyOf(targetOffsets.keySet()), null,
|
|
|
|
+ new Offsets(null, targetOffsets)),
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
|
|
|
|
|
+ new ConsumerPosition(TO_OFFSET, TOPIC, List.copyOf(targetOffsets.keySet()), null,
|
|
|
|
+ new Offsets(null, targetOffsets)),
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
@@ -213,50 +205,40 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
|
|
|
|
@Test
|
|
@Test
|
|
void pollWithTimestamps() {
|
|
void pollWithTimestamps() {
|
|
- Map<TopicPartition, Long> targetTimestamps = new HashMap<>();
|
|
|
|
- final Map<TopicPartition, List<Record>> perPartition =
|
|
|
|
- SENT_RECORDS.stream().collect(Collectors.groupingBy((r) -> r.tp));
|
|
|
|
- for (int i = 0; i < PARTITIONS; i++) {
|
|
|
|
- final List<Record> records = perPartition.get(new TopicPartition(TOPIC, i));
|
|
|
|
- int randRecordIdx = ThreadLocalRandom.current().nextInt(records.size());
|
|
|
|
- log.info("partition: {} position: {}", i, randRecordIdx);
|
|
|
|
- targetTimestamps.put(
|
|
|
|
- new TopicPartition(TOPIC, i),
|
|
|
|
- records.get(randRecordIdx).getTimestamp()
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
|
|
+ var tsStats = SENT_RECORDS.stream().mapToLong(Record::getTimestamp).summaryStatistics();
|
|
|
|
+ //choosing ts in the middle
|
|
|
|
+ long targetTimestamp = tsStats.getMin() + ((tsStats.getMax() - tsStats.getMin()) / 2);
|
|
|
|
|
|
- var forwardEmitter = new ForwardEmitter(
|
|
|
|
|
|
+ var forwardEmitter = new ForwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
|
|
|
- PARTITIONS * MSGS_PER_PARTITION,
|
|
|
|
|
|
+ new ConsumerPosition(FROM_TIMESTAMP, TOPIC, List.of(), targetTimestamp, null),
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ expectEmitter(
|
|
|
|
+ forwardEmitter,
|
|
|
|
+ SENT_RECORDS.stream()
|
|
|
|
+ .filter(r -> r.getTimestamp() >= targetTimestamp)
|
|
|
|
+ .map(Record::getValue)
|
|
|
|
+ .collect(Collectors.toList())
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(TIMESTAMP, TOPIC, targetTimestamps),
|
|
|
|
|
|
+ new ConsumerPosition(TO_TIMESTAMP, TOPIC, List.of(), targetTimestamp, null),
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
PARTITIONS * MSGS_PER_PARTITION,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
- var expectedValues = SENT_RECORDS.stream()
|
|
|
|
- .filter(r -> r.getTimestamp() >= targetTimestamps.get(r.getTp()))
|
|
|
|
- .map(Record::getValue)
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
-
|
|
|
|
- expectEmitter(forwardEmitter, expectedValues);
|
|
|
|
-
|
|
|
|
- expectedValues = SENT_RECORDS.stream()
|
|
|
|
- .filter(r -> r.getTimestamp() < targetTimestamps.get(r.getTp()))
|
|
|
|
- .map(Record::getValue)
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
-
|
|
|
|
- expectEmitter(backwardEmitter, expectedValues);
|
|
|
|
|
|
+ expectEmitter(
|
|
|
|
+ backwardEmitter,
|
|
|
|
+ SENT_RECORDS.stream()
|
|
|
|
+ .filter(r -> r.getTimestamp() < targetTimestamp)
|
|
|
|
+ .map(Record::getValue)
|
|
|
|
+ .collect(Collectors.toList())
|
|
|
|
+ );
|
|
}
|
|
}
|
|
|
|
|
|
@Test
|
|
@Test
|
|
@@ -267,12 +249,12 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
targetOffsets.put(new TopicPartition(TOPIC, i), (long) MSGS_PER_PARTITION);
|
|
targetOffsets.put(new TopicPartition(TOPIC, i), (long) MSGS_PER_PARTITION);
|
|
}
|
|
}
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(OFFSET, TOPIC, targetOffsets),
|
|
|
|
|
|
+ new ConsumerPosition(TO_OFFSET, TOPIC, List.copyOf(targetOffsets.keySet()), null,
|
|
|
|
+ new Offsets(null, targetOffsets)),
|
|
numMessages,
|
|
numMessages,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
@@ -294,12 +276,11 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
offsets.put(new TopicPartition(TOPIC, i), 0L);
|
|
offsets.put(new TopicPartition(TOPIC, i), 0L);
|
|
}
|
|
}
|
|
|
|
|
|
- var backwardEmitter = new BackwardEmitter(
|
|
|
|
|
|
+ var backwardEmitter = new BackwardRecordEmitter(
|
|
this::createConsumer,
|
|
this::createConsumer,
|
|
- new ConsumerPosition(OFFSET, TOPIC, offsets),
|
|
|
|
|
|
+ new ConsumerPosition(TO_OFFSET, TOPIC, List.copyOf(offsets.keySet()), null, new Offsets(null, offsets)),
|
|
100,
|
|
100,
|
|
RECORD_DESERIALIZER,
|
|
RECORD_DESERIALIZER,
|
|
- NOOP_FILTER,
|
|
|
|
PollingSettings.createDefault()
|
|
PollingSettings.createDefault()
|
|
);
|
|
);
|
|
|
|
|
|
@@ -339,20 +320,22 @@ class RecordEmitterTest extends AbstractIntegrationTest {
|
|
assertionsConsumer.accept(step.expectComplete().verifyThenAssertThat());
|
|
assertionsConsumer.accept(step.expectComplete().verifyThenAssertThat());
|
|
}
|
|
}
|
|
|
|
|
|
- private EnhancedConsumer createConsumer() {
|
|
|
|
|
|
+ private KafkaConsumer<Bytes, Bytes> createConsumer() {
|
|
return createConsumer(Map.of());
|
|
return createConsumer(Map.of());
|
|
}
|
|
}
|
|
|
|
|
|
- private EnhancedConsumer createConsumer(Map<String, Object> properties) {
|
|
|
|
|
|
+ private KafkaConsumer<Bytes, Bytes> createConsumer(Map<String, Object> properties) {
|
|
final Map<String, ? extends Serializable> map = Map.of(
|
|
final Map<String, ? extends Serializable> map = Map.of(
|
|
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers(),
|
|
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers(),
|
|
ConsumerConfig.GROUP_ID_CONFIG, UUID.randomUUID().toString(),
|
|
ConsumerConfig.GROUP_ID_CONFIG, UUID.randomUUID().toString(),
|
|
- ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 19 // to check multiple polls
|
|
|
|
|
|
+ ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 19, // to check multiple polls
|
|
|
|
+ ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, BytesDeserializer.class,
|
|
|
|
+ ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, BytesDeserializer.class
|
|
);
|
|
);
|
|
Properties props = new Properties();
|
|
Properties props = new Properties();
|
|
props.putAll(map);
|
|
props.putAll(map);
|
|
props.putAll(properties);
|
|
props.putAll(properties);
|
|
- return new EnhancedConsumer(props, PollingThrottler.noop(), ApplicationMetrics.noop());
|
|
|
|
|
|
+ return new KafkaConsumer<>(props);
|
|
}
|
|
}
|
|
|
|
|
|
@Value
|
|
@Value
|