package org.infinispan.persistence.remote.upgrade; import static org.infinispan.client.hotrod.ProtocolVersion.DEFAULT_PROTOCOL_VERSION; import static org.infinispan.test.TestingUtil.extractComponent; import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNull; import java.util.concurrent.TimeUnit; import org.infinispan.client.hotrod.MetadataValue; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.impl.RemoteCacheImpl; import org.infinispan.persistence.manager.PersistenceManager; import org.infinispan.persistence.remote.RemoteStore; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.test.TestingUtil; import org.infinispan.upgrade.RollingUpgradeManager; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @Test(testName = "upgrade.hotrod.HotRodUpgradeSynchronizerTest", groups = "functional") public class HotRodUpgradeSynchronizerTest extends AbstractInfinispanTest { private TestCluster sourceCluster, targetCluster; private static final String OLD_CACHE = "old-cache"; private static final String TEST_CACHE = HotRodUpgradeSynchronizerTest.class.getName(); private static final String OLD_PROTOCOL_VERSION = "2.0"; private static final String NEW_PROTOCOL_VERSION = DEFAULT_PROTOCOL_VERSION.toString(); @BeforeMethod public void setup() throws Exception { sourceCluster = new TestCluster.Builder().setName("sourceCluster").setNumMembers(1) .cache().name(OLD_CACHE) .cache().name(TEST_CACHE) .build(); targetCluster = new TestCluster.Builder().setName("targetCluster").setNumMembers(1) .cache().name(OLD_CACHE).remotePort(sourceCluster.getHotRodPort()).remoteProtocolVersion(OLD_PROTOCOL_VERSION) .cache().name(TEST_CACHE).remotePort(sourceCluster.getHotRodPort()).remoteProtocolVersion(NEW_PROTOCOL_VERSION) .build(); } private void fillCluster(TestCluster cluster, String cacheName) { for (char ch = 'A'; ch <= 'Z'; ch++) { String s = Character.toString(ch); cluster.getRemoteCache(cacheName).put(s, s, 20, TimeUnit.SECONDS, 30, TimeUnit.SECONDS); } } public void testSynchronizationViaKeyRecording() throws Exception { // Fill the old cluster with data fillCluster(sourceCluster, OLD_CACHE); // Verify access to some of the data from the new cluster assertEquals("A", targetCluster.getRemoteCache(OLD_CACHE).get("A")); sourceCluster.getRollingUpgradeManager(OLD_CACHE).recordKnownGlobalKeyset(); RollingUpgradeManager targetUpgradeManager = targetCluster.getRollingUpgradeManager(OLD_CACHE); targetUpgradeManager.synchronizeData("hotrod"); targetUpgradeManager.disconnectSource("hotrod"); assertEquals(sourceCluster.getRemoteCache(OLD_CACHE).size() - 1, targetCluster.getRemoteCache(OLD_CACHE).size()); MetadataValue<String> metadataValue = targetCluster.getRemoteCache(OLD_CACHE).getWithMetadata("A"); assertEquals(20, metadataValue.getLifespan()); assertEquals(30, metadataValue.getMaxIdle()); } public void testSynchronization() throws Exception { RemoteCache<String, String> sourceRemoteCache = sourceCluster.getRemoteCache(TEST_CACHE); RemoteCache<String, String> targetRemoteCache = targetCluster.getRemoteCache(TEST_CACHE); for (char ch = 'A'; ch <= 'Z'; ch++) { String s = Character.toString(ch); sourceRemoteCache.put(s, s, 20, TimeUnit.SECONDS, 30, TimeUnit.SECONDS); } // Verify access to some of the data from the new cluster assertEquals("A", targetRemoteCache.get("A")); RollingUpgradeManager upgradeManager = targetCluster.getRollingUpgradeManager(TEST_CACHE); long count = upgradeManager.synchronizeData("hotrod"); assertEquals(26, count); assertEquals(sourceCluster.getEmbeddedCache(TEST_CACHE).size(), targetCluster.getEmbeddedCache(TEST_CACHE).size()); upgradeManager.disconnectSource("hotrod"); MetadataValue<String> metadataValue = targetRemoteCache.getWithMetadata("Z"); assertEquals(20, metadataValue.getLifespan()); assertEquals(30, metadataValue.getMaxIdle()); } public void testSynchronizationWithClientChanges() throws Exception { // fill source cluster with data fillCluster(sourceCluster, TEST_CACHE); // Change data in the target cluster RemoteCache<String, String> remoteCache = targetCluster.getRemoteCache(TEST_CACHE); remoteCache.remove("G"); remoteCache.put("U", "I"); remoteCache.put("a", "a"); assertFalse(remoteCache.containsKey("G")); assertEquals("a", remoteCache.get("a")); assertEquals("I", remoteCache.get("U")); // Perform rolling upgrade RollingUpgradeManager rum = targetCluster.getRollingUpgradeManager(TEST_CACHE); rum.synchronizeData("hotrod"); rum.disconnectSource("hotrod"); // Verify data is consistent assertFalse(remoteCache.containsKey("G")); assertEquals("a", remoteCache.get("a")); assertEquals("I", remoteCache.get("U")); } @Test public void testSynchronizationWithInFlightUpdates() throws Exception { // fill source cluster with data fillCluster(sourceCluster, TEST_CACHE); RemoteCache<String, String> remoteCache = targetCluster.getRemoteCache(TEST_CACHE); doWhenSourceIterationReaches("M", targetCluster, TEST_CACHE, key -> remoteCache.put("M", "changed")); RollingUpgradeManager rum = targetCluster.getRollingUpgradeManager(TEST_CACHE); rum.synchronizeData("hotrod"); rum.disconnectSource("hotrod"); // Verify data is not overridden assertEquals("changed", remoteCache.get("M")); } @Test public void testSynchronizationWithInFlightDeletes() throws Exception { // fill source cluster with data fillCluster(sourceCluster, TEST_CACHE); RemoteCache<String, String> remoteCache = targetCluster.getRemoteCache(TEST_CACHE); doWhenSourceIterationReaches("L", targetCluster, TEST_CACHE, key -> remoteCache.remove("L")); RollingUpgradeManager rum = targetCluster.getRollingUpgradeManager(TEST_CACHE); rum.synchronizeData("hotrod"); rum.disconnectSource("hotrod"); // Verify data is not re-added assertNull(remoteCache.get("L")); } private void doWhenSourceIterationReaches(String key, TestCluster cluster, String cacheName, IterationCallBack callback) { cluster.getEmbeddedCaches(cacheName).forEach(c -> { PersistenceManager pm = extractComponent(c, PersistenceManager.class); RemoteStore remoteStore = pm.getStores(RemoteStore.class).iterator().next(); RemoteCacheImpl remoteCache = TestingUtil.extractField(remoteStore, "remoteCache"); RemoteCacheImpl spy = spy(remoteCache); doAnswer(invocation -> { Object[] params = invocation.getArguments(); CallbackRemoteIterator<Object> remoteCloseableIterator = new CallbackRemoteIterator<>(spy.getOperationsFactory(), (int) params[1], null, true); remoteCloseableIterator.addCallback(callback, key); remoteCloseableIterator.start(); return remoteCloseableIterator; }).when(spy).retrieveEntriesWithMetadata(isNull(), anyInt()); TestingUtil.replaceField(spy, "remoteCache", remoteStore, RemoteStore.class); }); } @AfterMethod public void tearDown() { sourceCluster.destroy(); targetCluster.destroy(); } }