/*
* Copyright 2014-2015 the original author or authors.
*
* 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.
*/
package org.springframework.xd.dirt.integration.bus.kafka;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.hamcrest.CoreMatchers;
import org.hamcrest.beans.HasPropertyWithValue;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.channel.interceptor.WireTap;
import org.springframework.integration.endpoint.AbstractEndpoint;
import org.springframework.integration.kafka.core.KafkaMessage;
import org.springframework.integration.kafka.core.Partition;
import org.springframework.integration.kafka.listener.AcknowledgingMessageListener;
import org.springframework.integration.kafka.listener.KafkaMessageListenerContainer;
import org.springframework.integration.kafka.listener.KafkaNativeOffsetManager;
import org.springframework.integration.kafka.listener.KafkaTopicOffsetManager;
import org.springframework.integration.kafka.listener.MessageListener;
import org.springframework.integration.kafka.support.KafkaHeaders;
import org.springframework.integration.kafka.support.ProducerConfiguration;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.xd.dirt.integration.bus.Binding;
import org.springframework.xd.dirt.integration.bus.BusProperties;
import org.springframework.xd.dirt.integration.bus.MessageBus;
import org.springframework.xd.dirt.integration.bus.PartitionCapableBusTests;
import org.springframework.xd.dirt.integration.bus.XdHeaders;
import org.springframework.xd.dirt.integration.kafka.KafkaMessageBus;
import org.springframework.xd.test.kafka.KafkaTestSupport;
import kafka.admin.AdminUtils$;
import kafka.api.OffsetRequest;
/**
* Integration tests for the {@link KafkaMessageBus}.
*
* @author Eric Bottard
* @author Marius Bogoevici
*/
public class KafkaMessageBusTests extends PartitionCapableBusTests {
@Rule
public KafkaTestSupport kafkaTestSupport = new KafkaTestSupport();
private KafkaTestMessageBus messageBus;
@Override
protected void busBindUnbindLatency() throws InterruptedException {
Thread.sleep(500);
}
@Override
protected MessageBus getMessageBus() {
if (messageBus == null) {
messageBus = createKafkaTestMessageBus();
}
return messageBus;
}
protected KafkaTestMessageBus createKafkaTestMessageBus() {
return new KafkaTestMessageBus(kafkaTestSupport, getCodec(), KafkaMessageBus.Mode.embeddedHeaders);
}
@Override
protected boolean usesExplicitRouting() {
return false;
}
@Override
public Spy spyOn(final String name) {
String topic = KafkaMessageBus.escapeTopicName(name);
KafkaTestMessageBus busWrapper = (KafkaTestMessageBus) getMessageBus();
// Rewind offset, as tests will have typically already sent the messages we're trying to consume
KafkaMessageListenerContainer messageListenerContainer = busWrapper.getCoreMessageBus().createMessageListenerContainer(
new Properties(), UUID.randomUUID().toString(), 1, topic, OffsetRequest.EarliestTime(), false);
final BlockingQueue<KafkaMessage> messages = new ArrayBlockingQueue<KafkaMessage>(10);
messageListenerContainer.setMessageListener(new MessageListener() {
@Override
public void onMessage(KafkaMessage message) {
messages.offer(message);
}
});
return new Spy() {
@Override
public Object receive(boolean expectNull) throws Exception {
return messages.poll(expectNull ? 50 : 5000, TimeUnit.MILLISECONDS);
}
};
}
@Test
@Override
@SuppressWarnings("unchecked")
public void testPartitioningWithSingleReceiver() throws Exception {
MessageBus bus = getMessageBus();
String topicName = "foo" + System.currentTimeMillis() + ".0";
try {
int partitionCount = 4;
Properties properties = new Properties();
properties.put("partitionKeyExtractorClass", "org.springframework.xd.dirt.integration.bus.PartitionTestSupport");
properties.put("partitionSelectorClass", "org.springframework.xd.dirt.integration.bus.PartitionTestSupport");
properties.put(BusProperties.MIN_PARTITION_COUNT, Integer.toString(partitionCount));
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
bus.bindProducer(topicName, moduleOutputChannel, properties);
bus.bindConsumer(topicName, moduleInputChannel, null);
int totalSent = 0;
for (int i = 0; i < partitionCount; i++) {
for (int j = 0; j < partitionCount + 1; j++ ) {
// the distribution is uneven across partitions, so that we can verify that the bus doesn't round robin
moduleOutputChannel.send(new GenericMessage<Object>(i*partitionCount + j));
totalSent++;
}
}
List<Message<Integer>> receivedMessages = new ArrayList<>();
for (int i = 0; i < totalSent; i++) {
assertTrue(receivedMessages.add((Message<Integer>) moduleInputChannel.receive(2000)));
}
assertThat(receivedMessages, hasSize(totalSent));
for (Message<Integer> receivedMessage : receivedMessages) {
int expectedPartition = receivedMessage.getPayload() % partitionCount;
assertThat(expectedPartition, equalTo(receivedMessage.getHeaders().get(KafkaHeaders.PARTITION_ID)));
}
}
finally {
bus.unbindConsumers(topicName);
bus.unbindProducers(topicName);
AdminUtils$.MODULE$.deleteTopic(kafkaTestSupport.getZkClient(),topicName);
}
}
@Test
public void testCompression() throws Exception {
final String[] codecs = new String[] { null, "none", "gzip", "snappy" };
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
MessageBus messageBus = getMessageBus();
for (String codec : codecs) {
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties props = new Properties();
if (codec != null) {
props.put(KafkaMessageBus.COMPRESSION_CODEC, codec);
}
messageBus.bindProducer("foo.0", moduleOutputChannel, props);
messageBus.bindConsumer("foo.0", moduleInputChannel, null);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
messageBus.unbindProducers("foo.0");
messageBus.unbindConsumers("foo.0");
}
}
@Test
public void testCustomPartitionCountOverridesDefaultIfLarger() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProperties = new Properties();
producerProperties.put(BusProperties.MIN_PARTITION_COUNT, "10");
Properties consumerProperties = new Properties();
consumerProperties.put(BusProperties.MIN_PARTITION_COUNT, "10");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
messageBus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"foo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(10));
messageBus.unbindProducers("foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("foo" + uniqueBindingId + ".0");
}
@Test
public void testCustomPartitionCountDoesNotOverrideModuleCountAndConcurrencyIfSmaller() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProps = new Properties();
producerProps.put(BusProperties.MIN_PARTITION_COUNT, "5");
producerProps.put(BusProperties.NEXT_MODULE_CONCURRENCY, "6");
Properties consumerProps = new Properties();
consumerProps.put(BusProperties.MIN_PARTITION_COUNT, "5");
consumerProps.put(BusProperties.CONCURRENCY, "6");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
messageBus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProps);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"foo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(6));
messageBus.unbindProducers("foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("foo" + uniqueBindingId + ".0");
}
@Test
public void testCustomPartitionCountOverridesModuleCountAndConcurrencyIfLarger() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProps = new Properties();
producerProps.put(BusProperties.MIN_PARTITION_COUNT, "6");
producerProps.put(BusProperties.NEXT_MODULE_CONCURRENCY, "5");
Properties consumerProps = new Properties();
consumerProps.put(BusProperties.MIN_PARTITION_COUNT, "6");
consumerProps.put(BusProperties.CONCURRENCY, "5");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProps);
messageBus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProps);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"foo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(6));
messageBus.unbindProducers("foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("foo" + uniqueBindingId + ".0");
}
@Test
public void testCustomPartitionCountDoesNotOverridePartitioningIfSmaller() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProperties = new Properties();
producerProperties.put(BusProperties.MIN_PARTITION_COUNT, "3");
producerProperties.put(BusProperties.NEXT_MODULE_COUNT, "5");
producerProperties.put(BusProperties.PARTITION_KEY_EXPRESSION, "payload");
Properties consumerProperties = new Properties();
consumerProperties.put(BusProperties.MIN_PARTITION_COUNT, "3");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
messageBus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"foo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(5));
messageBus.unbindProducers("foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("foo" + uniqueBindingId + ".0");
}
@Test
public void testCustomPartitionCountOverridesPartitioningIfLarger() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProperties = new Properties();
producerProperties.put(BusProperties.MIN_PARTITION_COUNT, "5");
producerProperties.put(BusProperties.NEXT_MODULE_COUNT, "3");
producerProperties.put(BusProperties.PARTITION_KEY_EXPRESSION, "payload");
Properties consumerProperties = new Properties();
consumerProperties.put(BusProperties.MIN_PARTITION_COUNT, "5");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
messageBus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"foo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(5));
messageBus.unbindProducers("foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("foo" + uniqueBindingId + ".0");
}
@Test
public void testNamedChannelPartitionCount() throws Exception {
byte[] ratherBigPayload = new byte[2048];
Arrays.fill(ratherBigPayload, (byte) 65);
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
Properties producerProperties = new Properties();
producerProperties.put(BusProperties.MIN_PARTITION_COUNT, "5");
Properties consumerProperties = new Properties();
consumerProperties.put(BusProperties.MIN_PARTITION_COUNT, "5");
long uniqueBindingId = System.currentTimeMillis();
messageBus.bindProducer("queue:foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
messageBus.bindConsumer("queue:foo" + uniqueBindingId + ".0", moduleInputChannel, consumerProperties);
messageBus.bindProducer("topic:foo" + uniqueBindingId + ".0", moduleOutputChannel, producerProperties);
Message<?> message = org.springframework.integration.support.MessageBuilder.withPayload(ratherBigPayload).build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertNotNull(inbound);
assertArrayEquals(ratherBigPayload, (byte[]) inbound.getPayload());
Collection<Partition> partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"queue_3Afoo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(5));
partitions = messageBus.getCoreMessageBus().getConnectionFactory().getPartitions(
"topic_3Afoo" + uniqueBindingId + ".0");
assertThat(partitions, hasSize(5));
messageBus.unbindProducers("queue:foo" + uniqueBindingId + ".0");
messageBus.unbindConsumers("queue:foo" + uniqueBindingId + ".0");
messageBus.unbindProducers("topic:foo" + uniqueBindingId + ".0");
}
@Test
public void testKafkaSpecificConsumerPropertiesAreSet() {
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
Properties consumerProperties = new Properties();
consumerProperties.put(KafkaMessageBus.AUTO_COMMIT_OFFSET_ENABLED, "false");
consumerProperties.put(KafkaMessageBus.FETCH_SIZE, "7");
consumerProperties.put(KafkaMessageBus.QUEUE_SIZE, "128");
long uniqueBindingId = System.currentTimeMillis();
DirectChannel inputChannel = new DirectChannel();
messageBus.bindConsumer("foo" + uniqueBindingId, inputChannel, consumerProperties);
DirectFieldAccessor accessor = new DirectFieldAccessor(messageBus.getCoreMessageBus());
@SuppressWarnings("unchecked")
List<Binding> bindings = (List<Binding>) accessor.getPropertyValue("bindings");
assertThat(bindings, hasSize(1));
Binding consumerBinding = bindings.get(0);
AbstractEndpoint bindingEndpoint = consumerBinding.getEndpoint();
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(bindingEndpoint);
KafkaMessageListenerContainer messageListenerContainer = ((KafkaMessageListenerContainer) endpointAccessor.getPropertyValue("messageListenerContainer") );
assertThat(messageListenerContainer.getQueueSize(), equalTo(128));
assertThat(messageListenerContainer.getMaxFetch(), equalTo(7));
assertThat(messageListenerContainer.getMessageListener(), instanceOf(AcknowledgingMessageListener.class));
messageBus.unbindConsumers("foo" + uniqueBindingId);
}
@Test
public void testKafkaSpecificProducerPropertiesAreSet() {
KafkaTestMessageBus messageBus = (KafkaTestMessageBus) getMessageBus();
Properties producerProperties = new Properties();
producerProperties.put(BusProperties.BATCH_SIZE, "34");
producerProperties.put(BusProperties.BATCH_TIMEOUT, "7");
long uniqueBindingId = System.currentTimeMillis();
DirectChannel inputChannel = new DirectChannel();
messageBus.bindProducer("foo" + uniqueBindingId, inputChannel, producerProperties);
DirectFieldAccessor accessor = new DirectFieldAccessor(messageBus.getCoreMessageBus());
@SuppressWarnings("unchecked")
List<Binding> bindings = (List<Binding>) accessor.getPropertyValue("bindings");
assertThat(bindings, hasSize(1));
Binding producerBinding = bindings.get(0);
AbstractEndpoint bindingEndpoint = producerBinding.getEndpoint();
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(bindingEndpoint);
MessageHandler messageHandler = ((MessageHandler) endpointAccessor.getPropertyValue("handler") );
DirectFieldAccessor messageHandlerAccessor = new DirectFieldAccessor(messageHandler);
ProducerConfiguration<?,?> producerConfiguration = (ProducerConfiguration<?, ?>) messageHandlerAccessor.getPropertyValue("producerConfiguration");
DirectFieldAccessor producerConfigurationAccessor = new DirectFieldAccessor(producerConfiguration);
@SuppressWarnings("rawtypes")
KafkaProducer producer = (KafkaProducer) producerConfigurationAccessor.getPropertyValue("producer");
DirectFieldAccessor producerAccessor = new DirectFieldAccessor(producer);
ProducerConfig producerConfig = (ProducerConfig)producerAccessor.getPropertyValue("producerConfig");
assertThat(producerConfig.getInt(ProducerConfig.BATCH_SIZE_CONFIG), equalTo(34));
assertThat(producerConfig.getLong(ProducerConfig.LINGER_MS_CONFIG), equalTo(7L));
messageBus.unbindProducers("foo" + uniqueBindingId);
}
@Test
public void testMoreHeaders() throws Exception {
KafkaTestMessageBus bus =
new KafkaTestMessageBus(kafkaTestSupport, getCodec(), KafkaMessageBus.Mode.embeddedHeaders,
"propagatedHeader");
Collection<String> headers = Arrays.asList(TestUtils.getPropertyValue(bus.getCoreMessageBus(), "headersToMap",
String[].class));
assertTrue(headers.contains("propagatedHeader"));
assertEquals(XdHeaders.STANDARD_HEADERS.length + 1, headers.size());
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
long uniqueBindingId = System.currentTimeMillis();
Properties emptyProperties = new Properties();
bus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, emptyProperties);
bus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, emptyProperties);
Message<?> message = org.springframework.integration.support.MessageBuilder.
withPayload("payload").setHeader("propagatedHeader","propagatedValue").build();
// Let the consumer actually bind to the producer before sending a msg
busBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = moduleInputChannel.receive(2000);
assertThat(inbound.getHeaders(), IsMapContaining.hasKey("propagatedHeader"));
assertThat((String)inbound.getHeaders().get("propagatedHeader"), equalTo("propagatedValue"));
bus.unbindProducers("foo" + uniqueBindingId + ".0");
bus.unbindConsumers("foo" + uniqueBindingId + ".0");
bus.cleanup();
}
@Test
@SuppressWarnings("unchecked")
public void testSendAndReceivePubSubWithMultipleConsumers() throws Exception {
MessageBus messageBus = getMessageBus();
DirectChannel graultUpstreamModuleOutputChannel = new DirectChannel();
// Test pub/sub by emulating how StreamPlugin handles taps
DirectChannel tapChannel = new DirectChannel();
QueueChannel graultDownstreamModuleInputChannel = new QueueChannel();
QueueChannel fooTapDownstreamInputChannel = new QueueChannel();
QueueChannel barTapDowstreamInput1Channel = new QueueChannel();
QueueChannel barTapDowstreamInput2Channel = new QueueChannel();
String originalTopic = "grault.0";
messageBus.bindProducer(originalTopic, graultUpstreamModuleOutputChannel, null);
messageBus.bindConsumer(originalTopic, graultDownstreamModuleInputChannel, null);
graultUpstreamModuleOutputChannel.addInterceptor(new WireTap(tapChannel));
Properties graultTapProducerProperties = new Properties();
// set the partition count to two, so that the tap has two partitions as well
// this will be necessary to robin messages across the competing consumers
graultTapProducerProperties.setProperty(BusProperties.MIN_PARTITION_COUNT, "2");
messageBus.bindPubSubProducer("tap:grault.http", tapChannel, graultTapProducerProperties);
// A new module is using the tap as an input channel
String fooTapName = messageBus.isCapable(MessageBus.Capability.DURABLE_PUBSUB) ? "foo.tap:grault.http" : "tap:grault.http";
messageBus.bindPubSubConsumer(fooTapName, fooTapDownstreamInputChannel, null);
// Another new module is using tap as an input channel
String barTapName = messageBus.isCapable(MessageBus.Capability.DURABLE_PUBSUB) ? "bar.tap:grault.http" : "tap:grault.http";
Properties barTap1Properties = new Properties();
barTap1Properties.setProperty(BusProperties.COUNT, "2");
barTap1Properties.setProperty(BusProperties.SEQUENCE, "1");
messageBus.bindPubSubConsumer(barTapName, barTapDowstreamInput1Channel, barTap1Properties);
Properties barTap2Properties = new Properties();
barTap2Properties.setProperty(BusProperties.COUNT, "2");
barTap2Properties.setProperty(BusProperties.SEQUENCE, "2");
messageBus.bindPubSubConsumer(barTapName, barTapDowstreamInput2Channel, barTap2Properties);
Message<?> message1 = MessageBuilder.withPayload("foo1").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
.build();
Message<?> message2 = MessageBuilder.withPayload("foo2").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
.build();
graultUpstreamModuleOutputChannel.send(message1);
graultUpstreamModuleOutputChannel.send(message2);
Message<?> graultDownstreamInbound = graultDownstreamModuleInputChannel.receive(5000);
assertNotNull(graultDownstreamInbound);
assertEquals("foo1", graultDownstreamInbound.getPayload());
assertNull(graultDownstreamInbound.getHeaders().get(XdHeaders.XD_ORIGINAL_CONTENT_TYPE));
assertEquals("foo/bar", graultDownstreamInbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
graultDownstreamInbound = graultDownstreamModuleInputChannel.receive(5000);
assertNotNull(graultDownstreamInbound);
assertEquals("foo2", graultDownstreamInbound.getPayload());
assertNull(graultDownstreamInbound.getHeaders().get(XdHeaders.XD_ORIGINAL_CONTENT_TYPE));
assertEquals("foo/bar", graultDownstreamInbound.getHeaders().get(MessageHeaders.CONTENT_TYPE));
List<Message<?>> tappedFoo = new ArrayList<>();
tappedFoo.add(fooTapDownstreamInputChannel.receive(5000));
tappedFoo.add(fooTapDownstreamInputChannel.receive(5000));
Message<?> tappedBar1 = barTapDowstreamInput1Channel.receive(5000);
Message<?> tappedBar2 = barTapDowstreamInput2Channel.receive(5000);
assertThat(tappedFoo,
CoreMatchers.<Message<?>>hasItems(hasProperty("payload", equalTo("foo1")),
hasProperty("payload", equalTo("foo2"))));
assertThat(tappedFoo,
everyItem(HasPropertyWithValue.<Message<?>>hasProperty("headers",
hasEntry(XdHeaders.XD_ORIGINAL_CONTENT_TYPE, null))));
assertThat(tappedFoo,
everyItem(HasPropertyWithValue.<Message<?>>hasProperty("headers",
hasEntry(MessageHeaders.CONTENT_TYPE, "foo/bar"))));
assertEquals("foo1", tappedBar1.getPayload());
assertNull(tappedBar1.getHeaders().get(XdHeaders.XD_ORIGINAL_CONTENT_TYPE));
assertEquals("foo/bar", tappedBar1.getHeaders().get(MessageHeaders.CONTENT_TYPE));
assertEquals("foo2", tappedBar2.getPayload());
assertNull(tappedBar2.getHeaders().get(XdHeaders.XD_ORIGINAL_CONTENT_TYPE));
assertEquals("foo/bar", tappedBar2.getHeaders().get(MessageHeaders.CONTENT_TYPE));
messageBus.unbindConsumers(fooTapName);
messageBus.unbindConsumers(originalTopic);
messageBus.unbindProducers(originalTopic);
messageBus.unbindProducers("tap:grault.http");
messageBus.unbindConsumers(barTapName);
assertTrue(getBindings(messageBus).isEmpty());
}
@Test
@SuppressWarnings("unchecked")
public void testNativeOffsetManagementEnabled() {
KafkaTestMessageBus bus =
new KafkaTestMessageBus(kafkaTestSupport, getCodec(),
KafkaMessageBus.OffsetManagement.kafkaNative);
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
long uniqueBindingId = System.currentTimeMillis();
bus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, null);
bus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, null);
Collection<Binding> bindings = (Collection<Binding>) getBindings(bus);
assertThat(bindings.size(),equalTo(2));
for (Binding binding : bindings) {
if ("consumer".equals(binding.getType())) {
AbstractEndpoint endpoint = binding.getEndpoint();
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(endpoint);
Object messageListenerContainer = endpointAccessor.getPropertyValue("messageListenerContainer");
DirectFieldAccessor containerAccessor = new DirectFieldAccessor(messageListenerContainer);
Object wrapperOffsetManager = containerAccessor.getPropertyValue("offsetManager");
DirectFieldAccessor offsetManagerAccessor = new DirectFieldAccessor(wrapperOffsetManager);
Object delegateOffsetManager = offsetManagerAccessor.getPropertyValue("delegate");
assertThat(delegateOffsetManager, instanceOf(KafkaNativeOffsetManager.class));
}
}
bus.unbindProducers("foo" + uniqueBindingId + ".0");
bus.unbindConsumers("foo" + uniqueBindingId + ".0");
bus.cleanup();
}
@Test
@SuppressWarnings("unchecked")
public void testTopicOffsetManagementEnabled() {
KafkaTestMessageBus bus =
new KafkaTestMessageBus(kafkaTestSupport, getCodec(),
KafkaMessageBus.OffsetManagement.kafkaTopic);
DirectChannel moduleOutputChannel = new DirectChannel();
QueueChannel moduleInputChannel = new QueueChannel();
long uniqueBindingId = System.currentTimeMillis();
bus.bindProducer("foo" + uniqueBindingId + ".0", moduleOutputChannel, null);
bus.bindConsumer("foo" + uniqueBindingId + ".0", moduleInputChannel, null);
Collection<Binding> bindings = (Collection<Binding>) getBindings(bus);
assertThat(bindings.size(),equalTo(2));
for (Binding binding : bindings) {
if ("consumer".equals(binding.getType())) {
AbstractEndpoint endpoint = binding.getEndpoint();
DirectFieldAccessor endpointAccessor = new DirectFieldAccessor(endpoint);
Object messageListenerContainer = endpointAccessor.getPropertyValue("messageListenerContainer");
DirectFieldAccessor containerAccessor = new DirectFieldAccessor(messageListenerContainer);
Object wrapperOffsetManager = containerAccessor.getPropertyValue("offsetManager");
DirectFieldAccessor offsetManagerAccessor = new DirectFieldAccessor(wrapperOffsetManager);
Object delegateOffsetManager = offsetManagerAccessor.getPropertyValue("delegate");
assertThat(delegateOffsetManager, instanceOf(KafkaTopicOffsetManager.class));
}
}
bus.unbindProducers("foo" + uniqueBindingId + ".0");
bus.unbindConsumers("foo" + uniqueBindingId + ".0");
bus.cleanup();
}
@Test
@Ignore("Kafka message bus does not support direct binding")
@Override
public void testDirectBinding() throws Exception {
}
}