package org.infinispan.statetransfer;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
import org.infinispan.commands.CommandsFactory;
import org.infinispan.commons.hash.MurmurHash3;
import org.infinispan.commons.util.SmallIntSet;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.container.DataContainer;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.ImmortalCacheEntry;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.distribution.TestAddress;
import org.infinispan.distribution.ch.KeyPartitioner;
import org.infinispan.distribution.ch.impl.DefaultConsistentHash;
import org.infinispan.distribution.ch.impl.DefaultConsistentHashFactory;
import org.infinispan.distribution.ch.impl.HashFunctionPartitioner;
import org.infinispan.notifications.cachelistener.cluster.ClusterCacheNotifier;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.remoting.inboundhandler.DeliverOrder;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.rpc.RpcOptionsBuilder;
import org.infinispan.remoting.transport.Address;
import org.infinispan.test.TestingUtil;
import org.infinispan.topology.CacheTopology;
import org.infinispan.topology.PersistentUUID;
import org.infinispan.topology.PersistentUUIDManager;
import org.infinispan.topology.PersistentUUIDManagerImpl;
import org.infinispan.transaction.impl.TransactionTable;
import org.infinispan.util.ByteString;
import org.infinispan.util.concurrent.IsolationLevel;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.mockito.stubbing.Answer;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* Test for StateProviderImpl.
*
* @author anistor@redhat.com
* @since 5.2
*/
@Test(groups = "functional", testName = "statetransfer.StateProviderTest")
public class StateProviderTest {
private static final Log log = LogFactory.getLog(StateProviderTest.class);
private static final TestAddress A = new TestAddress(0, "A");
private static final TestAddress B = new TestAddress(1, "B");
private static final TestAddress C = new TestAddress(2, "C");
private static final TestAddress D = new TestAddress(3, "D");
private static final TestAddress E = new TestAddress(4, "E");
private static final TestAddress F = new TestAddress(5, "F");
private static final TestAddress G = new TestAddress(6, "G");
private static final PersistentUUIDManager persistentUUIDManager = new PersistentUUIDManagerImpl();
static {
Arrays.asList(A, B, C, D, E, F, G).forEach(address -> persistentUUIDManager.addPersistentAddressMapping(address, PersistentUUID.randomUUID()));
}
private Configuration configuration;
private ExecutorService mockExecutorService;
private Cache cache;
private RpcManager rpcManager;
private CommandsFactory commandsFactory;
private ClusterCacheNotifier cacheNotifier;
private PersistenceManager persistenceManager;
private DataContainer dataContainer;
private TransactionTable transactionTable;
private StateTransferLock stateTransferLock;
private StateConsumer stateConsumer;
private CacheTopology cacheTopology;
private InternalEntryFactory ef;
@BeforeClass
public void setUp() {
// create cache configuration
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.clustering().invocationBatching().enable()
.clustering().cacheMode(CacheMode.DIST_SYNC)
.clustering().stateTransfer().timeout(10000)
.locking().lockAcquisitionTimeout(TestingUtil.shortTimeoutMillis())
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ);
configuration = cb.build();
mockExecutorService = mock(ExecutorService.class);
cache = mock(Cache.class);
when(cache.getName()).thenReturn("testCache");
rpcManager = mock(RpcManager.class);
commandsFactory = mock(CommandsFactory.class);
cacheNotifier = mock(ClusterCacheNotifier.class);
persistenceManager = mock(PersistenceManager.class);
dataContainer = mock(DataContainer.class);
transactionTable = mock(TransactionTable.class);
stateTransferLock = mock(StateTransferLock.class);
stateConsumer = mock(StateConsumer.class);
ef = mock(InternalEntryFactory.class);
when(stateConsumer.getCacheTopology()).thenAnswer(invocation -> cacheTopology);
}
public void test1() throws InterruptedException {
int numSegments = 4;
// create list of 6 members
List<Address> members1 = Arrays.asList(A, B, C, D, E, F);
List<Address> members2 = new ArrayList<>(members1);
members2.remove(A);
members2.remove(F);
members2.add(G);
// create CHes
KeyPartitioner keyPartitioner = new HashFunctionPartitioner();
DefaultConsistentHashFactory chf = new DefaultConsistentHashFactory();
DefaultConsistentHash ch1 = chf.create(MurmurHash3.getInstance(), 2, numSegments, members1, null);
DefaultConsistentHash ch2 = chf.updateMembers(ch1, members2, null);
// create dependencies
when(mockExecutorService.submit(any(Runnable.class)))
.thenAnswer((Answer<Future<?>>) invocation -> null);
when(rpcManager.getAddress()).thenReturn(A);
when(rpcManager.getRpcOptionsBuilder(any(ResponseMode.class))).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
return new RpcOptionsBuilder(10000, TimeUnit.MILLISECONDS, (ResponseMode) args[0], DeliverOrder.PER_SENDER);
});
// create state provider
StateProviderImpl stateProvider = new StateProviderImpl();
stateProvider.init(cache, mockExecutorService,
configuration, rpcManager, commandsFactory, cacheNotifier, persistenceManager,
dataContainer, transactionTable, stateTransferLock, stateConsumer, ef, keyPartitioner);
final List<InternalCacheEntry> cacheEntries = new ArrayList<>();
Object key1 = new TestKey("key1", 0, ch1);
Object key2 = new TestKey("key2", 0, ch1);
cacheEntries.add(new ImmortalCacheEntry(key1, "value1"));
cacheEntries.add(new ImmortalCacheEntry(key2, "value2"));
when(dataContainer.iterator()).thenAnswer(invocation -> cacheEntries.iterator());
when(transactionTable.getLocalTransactions()).thenReturn(Collections.emptyList());
when(transactionTable.getRemoteTransactions()).thenReturn(Collections.emptyList());
cacheTopology = new CacheTopology(1, 1, ch1, ch1, ch1, CacheTopology.Phase.READ_OLD_WRITE_ALL, ch1.getMembers(), persistentUUIDManager.mapAddresses(ch1.getMembers()));
stateProvider.onTopologyUpdate(cacheTopology, false);
log.debug("ch1: " + ch1);
Set<Integer> segmentsToRequest = ch1.getSegmentsForOwner(members1.get(0));
List<TransactionInfo> transactions = stateProvider.getTransactionsForSegments(members1.get(0), 1, segmentsToRequest);
assertEquals(0, transactions.size());
try {
stateProvider.getTransactionsForSegments(members1.get(0), 1, SmallIntSet.of(2, numSegments));
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException e) {
// expected
}
verifyNoMoreInteractions(stateTransferLock);
stateProvider.startOutboundTransfer(F, 1, Collections.singleton(0));
assertTrue(stateProvider.isStateTransferInProgress());
log.debug("ch2: " + ch2);
cacheTopology = new CacheTopology(2, 1, ch2, ch2, ch2, CacheTopology.Phase.READ_OLD_WRITE_ALL, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch2.getMembers()));
stateProvider.onTopologyUpdate(cacheTopology, true);
assertFalse(stateProvider.isStateTransferInProgress());
stateProvider.startOutboundTransfer(D, 1, Collections.singleton(0));
assertTrue(stateProvider.isStateTransferInProgress());
stateProvider.stop();
assertFalse(stateProvider.isStateTransferInProgress());
}
public void test2() throws InterruptedException {
int numSegments = 4;
// create list of 6 members
List<Address> members1 = Arrays.asList(A, B, C, D, E, F);
List<Address> members2 = new ArrayList<>(members1);
members2.remove(A);
members2.remove(F);
members2.add(G);
// create CHes
KeyPartitioner keyPartitioner = new HashFunctionPartitioner();
DefaultConsistentHashFactory chf = new DefaultConsistentHashFactory();
DefaultConsistentHash ch1 = chf.create(MurmurHash3.getInstance(), 2, numSegments, members1, null);
//todo [anistor] it seems that address 6 is not used for un-owned segments
DefaultConsistentHash ch2 = chf.updateMembers(ch1, members2, null);
when(commandsFactory.buildStateResponseCommand(any(Address.class), anyInt(), any(Collection.class)))
.thenAnswer(invocation -> new StateResponseCommand(ByteString.fromString("testCache"),
(Address) invocation.getArguments()[0],
((Integer) invocation.getArguments()[1]),
(Collection<StateChunk>) invocation.getArguments()[2]));
// create dependencies
when(rpcManager.getAddress()).thenReturn(A);
//rpcManager.invokeRemotelyInFuture(Collections.singleton(destination), cmd, false, sendFuture, timeout);
// doAnswer(new Answer<Map<Address, Response>>() {
// @Override
// public Map<Address, Response> answer(InvocationOnMock invocation) {
// Collection<Address> recipients = (Collection<Address>) invocation.getArguments()[0];
// ReplicableCommand rpcCommand = (ReplicableCommand) invocation.getArguments()[1];
// if (rpcCommand instanceof StateResponseCommand) {
// Map<Address, Response> results = new HashMap<Address, Response>();
// TestingUtil.sleepThread(10000, "RpcManager mock interrupted during invokeRemotelyInFuture(..)");
// return results;
// }
// return Collections.emptyMap();
// }
// }).when(rpcManager).invokeRemotelyInFuture(any(Collection.class), any(ReplicableCommand.class), any(RpcOptions.class),
// any(NotifyingNotifiableFuture.class));
when(rpcManager.getRpcOptionsBuilder(any(ResponseMode.class))).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
return new RpcOptionsBuilder(10000, TimeUnit.MILLISECONDS, (ResponseMode) args[0], DeliverOrder.PER_SENDER);
});
// create state provider
StateProviderImpl stateProvider = new StateProviderImpl();
stateProvider.init(cache, mockExecutorService,
configuration, rpcManager, commandsFactory, cacheNotifier, persistenceManager,
dataContainer, transactionTable, stateTransferLock, stateConsumer, ef, keyPartitioner);
final List<InternalCacheEntry> cacheEntries = new ArrayList<>();
Object key1 = new TestKey("key1", 0, ch1);
Object key2 = new TestKey("key2", 0, ch1);
Object key3 = new TestKey("key3", 1, ch1);
Object key4 = new TestKey("key4", 1, ch1);
cacheEntries.add(new ImmortalCacheEntry(key1, "value1"));
cacheEntries.add(new ImmortalCacheEntry(key2, "value2"));
cacheEntries.add(new ImmortalCacheEntry(key3, "value3"));
cacheEntries.add(new ImmortalCacheEntry(key4, "value4"));
when(dataContainer.iterator()).thenAnswer(invocation -> cacheEntries.iterator());
when(transactionTable.getLocalTransactions()).thenReturn(Collections.emptyList());
when(transactionTable.getRemoteTransactions()).thenReturn(Collections.emptyList());
cacheTopology = new CacheTopology(1, 1, ch1, ch1, ch1, CacheTopology.Phase.READ_OLD_WRITE_ALL, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch1.getMembers()));
stateProvider.onTopologyUpdate(cacheTopology, false);
log.debug("ch1: " + ch1);
Set<Integer> segmentsToRequest = ch1.getSegmentsForOwner(members1.get(0));
List<TransactionInfo> transactions = stateProvider.getTransactionsForSegments(members1.get(0), 1, segmentsToRequest);
assertEquals(0, transactions.size());
try {
stateProvider.getTransactionsForSegments(members1.get(0), 1, SmallIntSet.of(2, numSegments));
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException e) {
// expected
}
verifyNoMoreInteractions(stateTransferLock);
stateProvider.startOutboundTransfer(F, 1, Collections.singleton(0));
assertTrue(stateProvider.isStateTransferInProgress());
// TestingUtil.sleepThread(15000);
log.debug("ch2: " + ch2);
cacheTopology = new CacheTopology(2, 1, ch2, ch2, ch2, CacheTopology.Phase.READ_OLD_WRITE_ALL, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch2.getMembers()));
stateProvider.onTopologyUpdate(cacheTopology, false);
assertFalse(stateProvider.isStateTransferInProgress());
stateProvider.startOutboundTransfer(E, 1, Collections.singleton(0));
assertTrue(stateProvider.isStateTransferInProgress());
stateProvider.stop();
assertFalse(stateProvider.isStateTransferInProgress());
}
}