package org.infinispan.statetransfer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
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.CollectionFactory;
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.entries.ImmortalCacheEntry;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.context.InvocationContextFactory;
import org.infinispan.distribution.TestAddress;
import org.infinispan.distribution.TriangleOrderManager;
import org.infinispan.distribution.ch.impl.DefaultConsistentHash;
import org.infinispan.distribution.ch.impl.DefaultConsistentHashFactory;
import org.infinispan.distribution.ch.impl.HashFunctionPartitioner;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.remoting.inboundhandler.DeliverOrder;
import org.infinispan.remoting.responses.Response;
import org.infinispan.remoting.responses.SuccessfulResponse;
import org.infinispan.remoting.rpc.ResponseMode;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.rpc.RpcOptions;
import org.infinispan.remoting.rpc.RpcOptionsBuilder;
import org.infinispan.remoting.transport.Address;
import org.infinispan.remoting.transport.Transport;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.topology.CacheTopology;
import org.infinispan.topology.LocalTopologyManager;
import org.infinispan.topology.PersistentUUID;
import org.infinispan.topology.PersistentUUIDManager;
import org.infinispan.topology.PersistentUUIDManagerImpl;
import org.infinispan.transaction.impl.TransactionTable;
import org.infinispan.transaction.totalorder.TotalOrderManager;
import org.infinispan.util.ByteString;
import org.infinispan.util.concurrent.BlockingTaskAwareExecutorService;
import org.infinispan.util.concurrent.CommandAckCollector;
import org.infinispan.util.concurrent.IsolationLevel;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
/**
* Tests StateConsumerImpl.
*
* @author anistor@redhat.com
* @since 5.2
*/
@Test(groups = "functional", testName = "statetransfer.StateConsumerTest")
public class StateConsumerTest extends AbstractInfinispanTest {
private static final Log log = LogFactory.getLog(StateConsumerTest.class);
private ExecutorService pooledExecutorService;
@AfterMethod
public void tearDown() {
if (pooledExecutorService != null) {
pooledExecutorService.shutdownNow();
}
}
public void test1() throws Exception {
// 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 configuration = cb.build();
PersistentUUIDManager persistentUUIDManager = new PersistentUUIDManagerImpl();
// create list of 6 members
Address[] addresses = new Address[4];
for (int i = 0; i < 4; i++) {
addresses[i] = new TestAddress(i);
persistentUUIDManager.addPersistentAddressMapping(addresses[i], PersistentUUID.randomUUID());
}
List<Address> members1 = Arrays.asList(addresses[0], addresses[1], addresses[2], addresses[3]);
List<Address> members2 = Arrays.asList(addresses[0], addresses[1], addresses[2]);
// create CHes
DefaultConsistentHashFactory chf = new DefaultConsistentHashFactory();
DefaultConsistentHash ch1 = chf.create(MurmurHash3.getInstance(), 2, 40, members1, null);
final DefaultConsistentHash ch2 = chf.updateMembers(ch1, members2, null);
DefaultConsistentHash ch3 = chf.rebalance(ch2);
DefaultConsistentHash ch23 = chf.union(ch2, ch3);
log.debug(ch1);
log.debug(ch2);
// create dependencies
Cache cache = mock(Cache.class);
when(cache.getName()).thenReturn("testCache");
when(cache.getStatus()).thenReturn(ComponentStatus.RUNNING);
pooledExecutorService = new ThreadPoolExecutor(0, 20, 0L,
TimeUnit.MILLISECONDS, new SynchronousQueue<>(),
getTestThreadFactory("Worker"),
new ThreadPoolExecutor.CallerRunsPolicy());
StateTransferManager stateTransferManager = mock(StateTransferManager.class);
LocalTopologyManager localTopologyManager = mock(LocalTopologyManager.class);
CacheNotifier cacheNotifier = mock(CacheNotifier.class);
RpcManager rpcManager = mock(RpcManager.class);
Transport transport = mock(Transport.class);
CommandsFactory commandsFactory = mock(CommandsFactory.class);
PersistenceManager persistenceManager = mock(PersistenceManager.class);
DataContainer dataContainer = mock(DataContainer.class);
TransactionTable transactionTable = mock(TransactionTable.class);
StateTransferLock stateTransferLock = mock(StateTransferLock.class);
AsyncInterceptorChain interceptorChain = mock(AsyncInterceptorChain.class);
InvocationContextFactory icf = mock(InvocationContextFactory.class);
TotalOrderManager totalOrderManager = mock(TotalOrderManager.class);
BlockingTaskAwareExecutorService remoteCommandsExecutor = mock(BlockingTaskAwareExecutorService.class);
when(commandsFactory.buildStateRequestCommand(any(StateRequestCommand.Type.class), any(Address.class), anyInt(), any(SmallIntSet.class)))
.thenAnswer(invocation-> new StateRequestCommand(ByteString.fromString("cache1"),
(StateRequestCommand.Type) invocation.getArguments()[0],
(Address) invocation.getArguments()[1],
(Integer) invocation.getArguments()[2],
(Set) invocation.getArguments()[3]));
when(transport.getViewId()).thenReturn(1);
when(rpcManager.getAddress()).thenReturn(addresses[0]);
when(rpcManager.getTransport()).thenReturn(transport);
final Map<Address, Set<Integer>> requestedSegments = CollectionFactory.makeConcurrentMap();
final Set<Integer> flatRequestedSegments = new ConcurrentSkipListSet<>();
when(rpcManager.invokeRemotely(any(Collection.class), any(StateRequestCommand.class), any(RpcOptions.class)))
.thenAnswer(invocation -> {
Collection<Address> recipients = (Collection<Address>) invocation.getArguments()[0];
Address recipient = recipients.iterator().next();
StateRequestCommand cmd = (StateRequestCommand) invocation.getArguments()[1];
Map<Address, Response> results = new HashMap<>(1);
if (cmd.getType().equals(StateRequestCommand.Type.GET_TRANSACTIONS)) {
results.put(recipient, SuccessfulResponse.create(new ArrayList<TransactionInfo>()));
Set<Integer> segments = cmd.getSegments();
requestedSegments.put(recipient, segments);
flatRequestedSegments.addAll(segments);
} else if (cmd.getType().equals(StateRequestCommand.Type.START_STATE_TRANSFER)
|| cmd.getType().equals(StateRequestCommand.Type.CANCEL_STATE_TRANSFER)) {
results.put(recipient, SuccessfulResponse.SUCCESSFUL_EMPTY_RESPONSE);
}
return results;
});
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
final StateConsumerImpl stateConsumer = new StateConsumerImpl();
stateConsumer.init(cache, pooledExecutorService, stateTransferManager, localTopologyManager, interceptorChain, icf, configuration, rpcManager, null,
commandsFactory, persistenceManager, dataContainer, transactionTable, stateTransferLock, cacheNotifier,
totalOrderManager, remoteCommandsExecutor, new CommitManager(), new CommandAckCollector(), new TriangleOrderManager(0), null,
new HashFunctionPartitioner());
stateConsumer.start();
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());
assertFalse(stateConsumer.hasActiveTransfers());
// node 4 leaves
stateConsumer.onTopologyUpdate(new CacheTopology(1, 1, ch2, null, CacheTopology.Phase.NO_REBALANCE, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch2.getMembers())), false);
assertFalse(stateConsumer.hasActiveTransfers());
// start a rebalance
stateConsumer.onTopologyUpdate(new CacheTopology(2, 2, ch2, ch3, ch23, CacheTopology.Phase.READ_OLD_WRITE_ALL,
ch23.getMembers(), persistentUUIDManager.mapAddresses(ch23.getMembers())), true);
assertTrue(stateConsumer.hasActiveTransfers());
// check that all segments have been requested
Set<Integer> oldSegments = ch2.getSegmentsForOwner(addresses[0]);
final Set<Integer> newSegments = ch3.getSegmentsForOwner(addresses[0]);
newSegments.removeAll(oldSegments);
log.debugf("Rebalancing. Added segments=%s, old segments=%s", newSegments, oldSegments);
assertEquals(flatRequestedSegments, newSegments);
// simulate a cluster state recovery and return to ch2
Future<Object> future = fork(() -> {
stateConsumer.onTopologyUpdate(new CacheTopology(3, 2, ch2, null, CacheTopology.Phase.NO_REBALANCE, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch2.getMembers())), false);
return null;
});
stateConsumer.onTopologyUpdate(new CacheTopology(3, 2, ch2, null, CacheTopology.Phase.NO_REBALANCE, ch2.getMembers(), persistentUUIDManager.mapAddresses(ch2.getMembers())), false);
future.get();
assertFalse(stateConsumer.hasActiveTransfers());
// restart the rebalance
requestedSegments.clear();
stateConsumer.onTopologyUpdate(new CacheTopology(4, 4, ch2, ch3, ch23, CacheTopology.Phase.READ_OLD_WRITE_ALL,
ch23.getMembers(), persistentUUIDManager.mapAddresses(ch23.getMembers())), true);
assertTrue(stateConsumer.hasActiveTransfers());
assertEquals(flatRequestedSegments, newSegments);
// apply state
ArrayList<StateChunk> stateChunks = new ArrayList<>();
for (Integer segment : newSegments) {
stateChunks.add(new StateChunk(segment, Collections.emptyList(), true));
}
stateConsumer.applyState(addresses[1], 2, stateChunks);
stateConsumer.stop();
assertFalse(stateConsumer.hasActiveTransfers());
}
}