package org.infinispan.distribution.groups;
import static org.testng.AssertJUnit.assertEquals;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.infinispan.Cache;
import org.infinispan.commands.remote.GetKeysInGroupCommand;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.context.InvocationContext;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.interceptors.impl.EntryWrappingInterceptor;
import org.infinispan.remoting.transport.Address;
import org.infinispan.test.fwk.CheckPoint;
import org.infinispan.util.BaseControlledConsistentHashFactory;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
/**
* It tests the grouping advanced interface during the state transfer.
* <p/>
* Note: no tests were added for {@link org.infinispan.AdvancedCache#removeGroup(String)} because internally it uses the
* {@link org.infinispan.commands.write.RemoveCommand}. If the implementation changes, the new tests must be added to
* this class.
*
* @author Pedro Ruivo
* @since 7.0
*/
@Test(groups = "functional", testName = "distribution.groups.StateTransferGetGroupKeysTest")
public class StateTransferGetGroupKeysTest extends BaseUtilGroupTest {
@Override
public Object[] factory() {
return new Object[] {
new StateTransferGetGroupKeysTest(TestCacheFactory.PRIMARY_OWNER),
new StateTransferGetGroupKeysTest(TestCacheFactory.BACKUP_OWNER),
new StateTransferGetGroupKeysTest(TestCacheFactory.NON_OWNER),
};
}
public StateTransferGetGroupKeysTest() {
super(null);
}
protected StateTransferGetGroupKeysTest(TestCacheFactory factory) {
super(factory);
}
public void testGetGroupKeysDuringPrimaryOwnerChange() throws TimeoutException, InterruptedException, ExecutionException {
/*
* it tests multiple scenarios
* 1) when the ownership changes (when we execute the query in the primary owner and a new one is elected)
* 2) when the topology changes but the ownership doesn't (when we execute the query on the backup owner)
* 3) when the ownership changes and the query is executed in a non-owner
*/
final TestCache testCache = createTestCacheAndReset(GROUP, this.caches());
initCache(testCache.primaryOwner);
final BlockCommandInterceptor interceptor = injectBlockCommandInterceptorIfAbsent(extractTargetCache(testCache));
interceptor.open = false;
Future<Map<GroupKey, String>> future = fork(new Callable<Map<GroupKey, String>>() {
@Override
public Map<GroupKey, String> call() throws Exception {
return testCache.testCache.getGroup(GROUP);
}
});
interceptor.awaitCommandBlock();
addClusterEnabledCacheManager(createConfigurationBuilder());
waitForClusterToForm();
interceptor.unblockCommandAndOpen();
Map<GroupKey, String> groupKeySet = future.get();
Map<GroupKey, String> expectedGroupSet = createMap(0, 10);
AssertJUnit.assertEquals(expectedGroupSet, groupKeySet);
}
@AfterMethod
@Override
protected void clearContent() throws Throwable {
super.clearContent();
//in case we need to add more tests
if (cleanupAfterTest()) {
while (getCacheManagers().size() > 3) {
killMember(3);
}
while (getCacheManagers().size() < 3) {
addClusterEnabledCacheManager(createConfigurationBuilder());
}
waitForClusterToForm();
}
}
@Override
protected final void resetCaches(List<Cache<GroupKey, String>> cacheList) {
for (Cache cache : cacheList) {
AsyncInterceptorChain chain = cache.getAdvancedCache().getAsyncInterceptorChain();
BlockCommandInterceptor interceptor = chain.findInterceptorWithClass(BlockCommandInterceptor.class);
if (interceptor != null) {
interceptor.reset();
}
}
}
@Override
protected void createCacheManagers() throws Throwable {
createClusteredCaches(3, createConfigurationBuilder());
}
private static BlockCommandInterceptor injectBlockCommandInterceptorIfAbsent(Cache<GroupKey, String> cache) {
AsyncInterceptorChain chain = cache.getAdvancedCache().getAsyncInterceptorChain();
BlockCommandInterceptor interceptor = chain.findInterceptorWithClass(BlockCommandInterceptor.class);
if (interceptor == null) {
interceptor = new BlockCommandInterceptor();
chain.addInterceptorAfter(interceptor, EntryWrappingInterceptor.class);
}
interceptor.reset();
return interceptor;
}
private static ConfigurationBuilder createConfigurationBuilder() {
ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, false);
builder.clustering().stateTransfer().fetchInMemoryState(true);
builder.clustering().hash().groups().enabled(true);
builder.clustering().hash().numOwners(2);
builder.clustering().hash().numSegments(1);
builder.clustering().hash().consistentHashFactory(new CustomConsistentHashFactory());
return builder;
}
private static class CustomConsistentHashFactory extends BaseControlledConsistentHashFactory {
private CustomConsistentHashFactory() {
super(1);
}
@Override
protected List<Address> createOwnersCollection(List<Address> members, int numberOfOwners, int segmentIndex) {
assertEquals(2, numberOfOwners);
if (members.size() == 1)
return Arrays.asList(members.get(0));
else if (members.size() == 2)
return Arrays.asList(members.get(0), members.get(1));
else
return Arrays.asList(members.get(members.size() - 1), members.get(0));
}
}
private static class BlockCommandInterceptor extends CommandInterceptor {
private volatile CheckPoint checkPoint;
private volatile boolean open;
private BlockCommandInterceptor() {
checkPoint = new CheckPoint();
}
@Override
public Object visitGetKeysInGroupCommand(InvocationContext ctx, GetKeysInGroupCommand command) throws Throwable {
if (!open) {
checkPoint.trigger("before");
checkPoint.awaitStrict("after", 30, TimeUnit.SECONDS);
}
return invokeNextInterceptor(ctx, command);
}
public final void awaitCommandBlock() throws TimeoutException, InterruptedException {
checkPoint.awaitStrict("before", 30, TimeUnit.SECONDS);
}
public final void unblockCommand() {
checkPoint.trigger("after");
}
public final void unblockCommandAndOpen() {
open = true;
checkPoint.trigger("after");
}
public final void reset() {
open = true;
checkPoint = new CheckPoint();
}
}
}