/*
* Copyright 2016-2017 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.integration.jdbc.leader;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Level;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.integration.jdbc.lock.DefaultLockRepository;
import org.springframework.integration.jdbc.lock.JdbcLockRegistry;
import org.springframework.integration.leader.Context;
import org.springframework.integration.leader.DefaultCandidate;
import org.springframework.integration.leader.event.LeaderEventPublisher;
import org.springframework.integration.support.leader.LockRegistryLeaderInitiator;
import org.springframework.integration.test.rule.Log4jLevelAdjuster;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
/**
* @author Artem Bilan
* @author Gary Russell
* @since 4.3.1
*/
public class JdbcLockRegistryLeaderInitiatorTests {
public static EmbeddedDatabase dataSource;
@Rule
public Log4jLevelAdjuster adjuster = new Log4jLevelAdjuster(Level.DEBUG, "org.springframework.integration",
"org.springframework.integration.jdbc", "org.springframework.jdbc", "org.apache.derby");
@BeforeClass
public static void init() {
dataSource = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:/org/springframework/integration/jdbc/schema-h2.sql")
.build();
}
@AfterClass
public static void destroy() {
dataSource.shutdown();
}
@Test
public void testDistributedLeaderElection() throws Exception {
CountDownLatch granted = new CountDownLatch(1);
CountingPublisher countingPublisher = new CountingPublisher(granted);
List<LockRegistryLeaderInitiator> initiators = new ArrayList<LockRegistryLeaderInitiator>();
for (int i = 0; i < 2; i++) {
DefaultLockRepository lockRepository = new DefaultLockRepository(dataSource);
lockRepository.afterPropertiesSet();
LockRegistryLeaderInitiator initiator = new LockRegistryLeaderInitiator(
new JdbcLockRegistry(lockRepository),
new DefaultCandidate("foo", "bar"));
initiator.setLeaderEventPublisher(countingPublisher);
initiators.add(initiator);
}
for (LockRegistryLeaderInitiator initiator : initiators) {
initiator.start();
}
assertThat(granted.await(10, TimeUnit.SECONDS), is(true));
LockRegistryLeaderInitiator initiator1 = countingPublisher.initiator;
LockRegistryLeaderInitiator initiator2 = null;
for (LockRegistryLeaderInitiator initiator : initiators) {
if (initiator != initiator1) {
initiator2 = initiator;
break;
}
}
assertNotNull(initiator2);
assertThat(initiator1.getContext().isLeader(), is(true));
assertThat(initiator2.getContext().isLeader(), is(false));
final CountDownLatch granted1 = new CountDownLatch(1);
final CountDownLatch granted2 = new CountDownLatch(1);
CountDownLatch revoked1 = new CountDownLatch(1);
CountDownLatch revoked2 = new CountDownLatch(1);
initiator1.setLeaderEventPublisher(new CountingPublisher(granted1, revoked1) {
@Override
public void publishOnRevoked(Object source, Context context, String role) {
try {
// It's difficult to see round-robin election, so block one initiator until the second is elected.
assertThat(granted2.await(20, TimeUnit.SECONDS), is(true));
}
catch (InterruptedException e) {
// No op
}
super.publishOnRevoked(source, context, role);
}
});
initiator2.setLeaderEventPublisher(new CountingPublisher(granted2, revoked2) {
@Override
public void publishOnRevoked(Object source, Context context, String role) {
try {
// It's difficult to see round-robin election, so block one initiator until the second is elected.
assertThat(granted1.await(20, TimeUnit.SECONDS), is(true));
}
catch (InterruptedException e) {
// No op
}
super.publishOnRevoked(source, context, role);
}
});
initiator1.getContext().yield();
assertThat(revoked1.await(20, TimeUnit.SECONDS), is(true));
assertThat(initiator2.getContext().isLeader(), is(true));
assertThat(initiator1.getContext().isLeader(), is(false));
initiator2.getContext().yield();
assertThat(revoked2.await(20, TimeUnit.SECONDS), is(true));
assertThat(initiator1.getContext().isLeader(), is(true));
assertThat(initiator2.getContext().isLeader(), is(false));
initiator2.stop();
CountDownLatch revoked11 = new CountDownLatch(1);
initiator1.setLeaderEventPublisher(new CountingPublisher(new CountDownLatch(1), revoked11));
initiator1.getContext().yield();
assertThat(revoked11.await(20, TimeUnit.SECONDS), is(true));
assertThat(initiator1.getContext().isLeader(), is(false));
initiator1.stop();
}
private static class CountingPublisher implements LeaderEventPublisher {
private final CountDownLatch granted;
private final CountDownLatch revoked;
private volatile LockRegistryLeaderInitiator initiator;
CountingPublisher(CountDownLatch granted, CountDownLatch revoked) {
this.granted = granted;
this.revoked = revoked;
}
CountingPublisher(CountDownLatch granted) {
this(granted, new CountDownLatch(1));
}
@Override
public void publishOnRevoked(Object source, Context context, String role) {
this.revoked.countDown();
}
@Override
public void publishOnGranted(Object source, Context context, String role) {
this.initiator = (LockRegistryLeaderInitiator) source;
this.granted.countDown();
}
}
}