/*
* Copyright 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.rabbit;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelCallback;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.xd.dirt.integration.bus.RabbitManagementUtils;
import org.springframework.xd.dirt.integration.bus.BusUtils;
import org.springframework.xd.dirt.integration.bus.MessageBusSupport;
import org.springframework.xd.dirt.integration.bus.RabbitAdminException;
import org.springframework.xd.dirt.plugins.AbstractJobPlugin;
import org.springframework.xd.dirt.plugins.job.JobEventsListenerPlugin;
import org.springframework.xd.test.rabbit.RabbitAdminTestSupport;
import org.springframework.xd.test.rabbit.RabbitTestSupport;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
/**
* @author Gary Russell
* @since 1.2
*/
public class RabbitBusCleanerTests {
private static final String XDBUS_PREFIX = "xdbus.";
@Rule
public RabbitAdminTestSupport adminTest = new RabbitAdminTestSupport();
@Rule
public RabbitTestSupport test = new RabbitTestSupport();
@Test
public void testCleanStream() {
final RabbitBusCleaner cleaner = new RabbitBusCleaner();
final RestTemplate template = RabbitManagementUtils.buildRestTemplate("http://localhost:15672", "guest", "guest");
final String stream1 = UUID.randomUUID().toString();
String stream2 = stream1 + "-1";
String firstQueue = null;
for (int i = 0; i < 5; i++) {
String queue1Name = MessageBusSupport.applyPrefix(XDBUS_PREFIX,
BusUtils.constructPipeName(stream1, i));
String queue2Name = MessageBusSupport.applyPrefix(XDBUS_PREFIX,
BusUtils.constructPipeName(stream2, i));
if (firstQueue == null) {
firstQueue = queue1Name;
}
URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
.pathSegment("{vhost}", "{queue}")
.buildAndExpand("/", queue1Name)
.encode().toUri();
template.put(uri, new AmqpQueue(false, true));
uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
.pathSegment("{vhost}", "{queue}")
.buildAndExpand("/", queue2Name)
.encode().toUri();
template.put(uri, new AmqpQueue(false, true));
uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
.pathSegment("{vhost}", "{queue}")
.buildAndExpand("/", MessageBusSupport.constructDLQName(queue1Name)).encode().toUri();
template.put(uri, new AmqpQueue(false, true));
}
CachingConnectionFactory connectionFactory = test.getResource();
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
final FanoutExchange fanout1 = new FanoutExchange(
MessageBusSupport.applyPrefix(XDBUS_PREFIX, MessageBusSupport.applyPubSub(
BusUtils.constructTapPrefix(stream1) + ".foo.bar")));
rabbitAdmin.declareExchange(fanout1);
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(fanout1));
final FanoutExchange fanout2 = new FanoutExchange(
MessageBusSupport.applyPrefix(XDBUS_PREFIX, MessageBusSupport.applyPubSub(
BusUtils.constructTapPrefix(stream2) + ".foo.bar")));
rabbitAdmin.declareExchange(fanout2);
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(fanout2));
new RabbitTemplate(connectionFactory).execute(new ChannelCallback<Void>() {
@Override
public Void doInRabbit(Channel channel) throws Exception {
String queueName = MessageBusSupport.applyPrefix(XDBUS_PREFIX,
BusUtils.constructPipeName(stream1, 4));
String consumerTag = channel.basicConsume(queueName, new DefaultConsumer(channel));
try {
waitForConsumerStateNot(queueName, 0);
cleaner.clean(stream1, false);
fail("Expected exception");
}
catch (RabbitAdminException e) {
assertEquals("Queue " + queueName + " is in use", e.getMessage());
}
channel.basicCancel(consumerTag);
waitForConsumerStateNot(queueName, 1);
try {
cleaner.clean(stream1, false);
fail("Expected exception");
}
catch (RabbitAdminException e) {
assertThat(e.getMessage(), startsWith("Cannot delete exchange " +
fanout1.getName() + "; it has bindings:"));
}
return null;
}
private void waitForConsumerStateNot(String queueName, int state) throws InterruptedException {
int n = 0;
URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues").pathSegment(
"{vhost}", "{queue}")
.buildAndExpand("/", queueName).encode().toUri();
while (n++ < 100) {
@SuppressWarnings("unchecked")
Map<String, Object> queueInfo = template.getForObject(uri, Map.class);
if (!queueInfo.get("consumers").equals(Integer.valueOf(state))) {
break;
}
Thread.sleep(100);
}
assertTrue("Consumer state remained at " + state + " after 10 seconds", n < 100);
}
});
rabbitAdmin.deleteExchange(fanout1.getName()); // easier than deleting the binding
rabbitAdmin.declareExchange(fanout1);
connectionFactory.destroy();
Map<String, List<String>> cleanedMap = cleaner.clean(stream1, false);
assertEquals(2, cleanedMap.size());
List<String> cleanedQueues = cleanedMap.get("queues");
// should *not* clean stream2
assertEquals(10, cleanedQueues.size());
for (int i = 0; i < 5; i++) {
assertEquals(XDBUS_PREFIX + stream1 + "." + i, cleanedQueues.get(i * 2));
assertEquals(XDBUS_PREFIX + stream1 + "." + i + ".dlq", cleanedQueues.get(i * 2 + 1));
}
List<String> cleanedExchanges = cleanedMap.get("exchanges");
assertEquals(1, cleanedExchanges.size());
assertEquals(fanout1.getName(), cleanedExchanges.get(0));
// wild card *should* clean stream2
cleanedMap = cleaner.clean(stream1 + "*", false);
assertEquals(2, cleanedMap.size());
cleanedQueues = cleanedMap.get("queues");
assertEquals(5, cleanedQueues.size());
for (int i = 0; i < 5; i++) {
assertEquals(XDBUS_PREFIX + stream2 + "." + i, cleanedQueues.get(i));
}
cleanedExchanges = cleanedMap.get("exchanges");
assertEquals(1, cleanedExchanges.size());
assertEquals(fanout2.getName(), cleanedExchanges.get(0));
}
@Test
public void testCleanJob() {
final RabbitBusCleaner cleaner = new RabbitBusCleaner();
final String job1 = UUID.randomUUID().toString();
String job2 = job1 + "-1";
Set<String> jobExchanges = new HashSet<>(JobEventsListenerPlugin.getEventListenerChannels(job1).values());
jobExchanges.add(JobEventsListenerPlugin.getEventListenerChannelName(job1));
jobExchanges.addAll(JobEventsListenerPlugin.getEventListenerChannels(job2).values());
jobExchanges.add(JobEventsListenerPlugin.getEventListenerChannelName(job2));
CachingConnectionFactory connectionFactory = test.getResource();
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
for (String exchange : jobExchanges) {
FanoutExchange fanout = new FanoutExchange(
MessageBusSupport.applyPrefix(XDBUS_PREFIX, MessageBusSupport.applyPubSub(exchange)));
rabbitAdmin.declareExchange(fanout);
}
Queue queue1 = new Queue(MessageBusSupport.applyPrefix(XDBUS_PREFIX,
AbstractJobPlugin.getJobChannelName(job1)));
Queue jobRequestQueue1 = new Queue(MessageBusSupport.applyPrefix(XDBUS_PREFIX,
MessageBusSupport.applyRequests(AbstractJobPlugin.getJobChannelName(job1 + ".0"))));
rabbitAdmin.declareQueue(queue1);
rabbitAdmin.declareQueue(jobRequestQueue1);
Queue queue2 = new Queue(MessageBusSupport.applyPrefix(XDBUS_PREFIX,
AbstractJobPlugin.getJobChannelName(job2)));
Queue jobRequestQueue2 = new Queue(MessageBusSupport.applyPrefix(XDBUS_PREFIX,
MessageBusSupport.applyRequests(AbstractJobPlugin.getJobChannelName(job2 + ".0"))));
rabbitAdmin.declareQueue(queue2);
rabbitAdmin.declareQueue(jobRequestQueue2);
Map<String, List<String>> cleanedMap = cleaner.clean(job1, true);
assertEquals(2, cleanedMap.size());
List<String> cleanedQueues = cleanedMap.get("queues");
assertEquals(2, cleanedQueues.size());
assertThat(cleanedQueues.get(0), anyOf(equalTo(queue1.getName()), equalTo(jobRequestQueue1.getName())));
assertThat(cleanedQueues.get(1), anyOf(equalTo(queue1.getName()), equalTo(jobRequestQueue1.getName())));
List<String> cleanedExchanges = cleanedMap.get("exchanges");
// should *not* clean job2
assertEquals(6, cleanedExchanges.size());
// wild card *should* clean job2
cleanedMap = cleaner.clean(job1 + "*", true);
assertEquals(2, cleanedMap.size());
cleanedQueues = cleanedMap.get("queues");
assertEquals(2, cleanedQueues.size());
assertThat(cleanedQueues.get(0), anyOf(equalTo(queue2.getName()), equalTo(jobRequestQueue2.getName())));
assertThat(cleanedQueues.get(1), anyOf(equalTo(queue2.getName()), equalTo(jobRequestQueue2.getName())));
cleanedExchanges = cleanedMap.get("exchanges");
assertEquals(6, cleanedExchanges.size());
}
public static class AmqpQueue {
private boolean autoDelete;
private boolean durable;
public AmqpQueue(boolean autoDelete, boolean durable) {
this.autoDelete = autoDelete;
this.durable = durable;
}
@JsonProperty("auto_delete")
protected boolean isAutoDelete() {
return autoDelete;
}
protected void setAutoDelete(boolean autoDelete) {
this.autoDelete = autoDelete;
}
protected boolean isDurable() {
return durable;
}
protected void setDurable(boolean durable) {
this.durable = durable;
}
}
}