package org.infinispan.xsite;
import static java.lang.String.format;
import static org.infinispan.test.TestingUtil.extractGlobalComponent;
import static org.infinispan.test.TestingUtil.k;
import static org.infinispan.test.TestingUtil.v;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.BackupConfiguration;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.AbstractDelegatingTransport;
import org.infinispan.remoting.transport.Transport;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.CleanupAfterTest;
import org.infinispan.util.NotifierLatch;
import org.infinispan.xsite.statetransfer.XSiteStatePushCommand;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
/**
* Test for {@link GlobalXSiteAdminOperations}.
*
* @author Pedro Ruivo
* @since 8.1
*/
@CleanupAfterTest
@Test(groups = "xsite", testName = "xsite.GlobalXSiteAdminOpsTest")
public class GlobalXSiteAdminOpsTest extends AbstractMultipleSitesTest {
protected static ConfigurationBuilder newConfiguration() {
return getDefaultClusteredCacheConfig(CacheMode.REPL_SYNC, false);
}
public void testTakeSiteOffline(Method m) {
final String key = k(m);
final String value = v(m);
assertAllCachesEmpty();
assertSiteStatusInAllCaches(XSiteAdminOperations.ONLINE);
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).takeSiteOffline(siteName(1));
assertSiteStatus(0, 0, null, 1, XSiteAdminOperations.OFFLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, XSiteAdminOperations.OFFLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, XSiteAdminOperations.ONLINE);
//double check with data
putInAllCache(key, value);
assertValueInAllCachesInPrimarySite(key, value); //all caches should have the value in primary site
assertCacheEmpty(1, 0, null);
assertCacheEmpty(1, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertValueInCache(2, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertValueInCache(2, CacheType.BACKUP_TO_SITE_2.name(), key, value);
}
public void testBringSiteOnline(Method m) {
final String key = k(m);
final String value = v(m);
assertAllCachesEmpty();
setSitesStatus(false);
assertSiteStatusInAllCaches(XSiteAdminOperations.OFFLINE);
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).bringSiteOnline(siteName(1));
assertSiteStatus(0, 0, null, 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, XSiteAdminOperations.OFFLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, XSiteAdminOperations.OFFLINE);
//double check with data
putInAllCache(key, value);
assertValueInAllCachesInPrimarySite(key, value); //all caches should have the value in primary site
assertValueInCache(1, null, key, value);
assertValueInCache(1, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
}
public void testPushState(Method m) {
final String key = k(m);
final String value = v(m);
assertAllCachesEmpty();
setSitesStatus(false);
assertSiteStatusInAllCaches(XSiteAdminOperations.OFFLINE);
putInAllCache(key, value);
assertValueInAllCachesInPrimarySite(key, value);
//check the value is not in the backups
assertCacheEmpty(1, 0, null);
assertCacheEmpty(1, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).pushState(siteName(1));
awaitXSiteStateTransfer();
//check state and data
assertSiteStatus(0, 0, null, 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, XSiteAdminOperations.OFFLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, XSiteAdminOperations.OFFLINE);
assertValueInCache(1, null, key, value);
assertValueInCache(1, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).pushState(siteName(2));
awaitXSiteStateTransfer();
//check state and data
assertSiteStatus(0, 0, null, 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, XSiteAdminOperations.ONLINE);
assertValueInCache(1, null, key, value);
assertValueInCache(1, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertValueInCache(2, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertValueInCache(2, CacheType.BACKUP_TO_SITE_2.name(), key, value);
}
public void testCancelPushState(Method m) {
final String key = k(m);
final String value = v(m);
assertAllCachesEmpty();
setSitesStatus(false);
assertSiteStatusInAllCaches(XSiteAdminOperations.OFFLINE);
putInAllCache(key, value);
assertValueInAllCachesInPrimarySite(key, value);
//check the value is not in the backups
assertCacheEmpty(1, 0, null);
assertCacheEmpty(1, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
List<BlockingTransport> blockingTransportList = getBlockingTransport(0, true);
blockingTransportList.forEach(BlockingTransport::blockCommands);
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).pushState(siteName(1));
extractGlobalComponent(site(0).cacheManagers().get(0), GlobalXSiteAdminOperations.class).cancelPushState(siteName(1));
//check state and data
assertSiteStatus(0, 0, null, 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, XSiteAdminOperations.ONLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, XSiteAdminOperations.OFFLINE);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, XSiteAdminOperations.OFFLINE);
assertCacheEmpty(1, 0, null);
assertCacheEmpty(1, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
blockingTransportList.forEach(BlockingTransport::unblockCommands);
}
@AfterMethod(alwaysRun = true)
public void resetStatusAfterMethod() {
setSitesStatus(true);
getBlockingTransport(0, false).forEach(BlockingTransport::unblockCommands);
}
@Override
protected ConfigurationBuilder defaultConfigurationForSite(int siteIndex) {
if (siteIndex == 0) {
//default cache will backup to site_1
ConfigurationBuilder builder = newConfiguration();
builder.sites().addBackup().site(siteName(1)).strategy(BackupConfiguration.BackupStrategy.SYNC);
return builder;
} else {
return newConfiguration();
}
}
@Override
protected int defaultNumberOfSites() {
return 3;
}
@Override
protected void afterSitesCreated() {
super.afterSitesCreated();
ConfigurationBuilder builder = newConfiguration();
builder.sites().addBackup().site(siteName(2)).strategy(BackupConfiguration.BackupStrategy.SYNC);
defineInSite(site(0), CacheType.BACKUP_TO_SITE_2.name(), builder.build());
defineInSite(site(2), CacheType.BACKUP_TO_SITE_2.name(), newConfiguration().build());
builder = newConfiguration();
builder.sites().addBackup().site(siteName(1)).strategy(BackupConfiguration.BackupStrategy.SYNC);
builder.sites().addBackup().site(siteName(2)).strategy(BackupConfiguration.BackupStrategy.SYNC);
defineInSite(site(0), CacheType.BACKUP_TO_SITE_1_AND_2.name(), builder.build());
defineInSite(site(1), CacheType.BACKUP_TO_SITE_1_AND_2.name(), newConfiguration().build());
defineInSite(site(2), CacheType.BACKUP_TO_SITE_1_AND_2.name(), newConfiguration().build());
defineInSite(site(0), CacheType.NO_BACKUP.name(), newConfiguration().build());
//wait for caches in primary cluster
site(0).waitForClusterToForm(null);
site(0).waitForClusterToForm(CacheType.BACKUP_TO_SITE_1_AND_2.name());
site(0).waitForClusterToForm(CacheType.BACKUP_TO_SITE_1_AND_2.name());
site(0).waitForClusterToForm(CacheType.BACKUP_TO_SITE_2.name());
//wait for caches in backup site 1
site(1).waitForClusterToForm(null);
site(1).waitForClusterToForm(CacheType.BACKUP_TO_SITE_1_AND_2.name());
//wait for caches in backup site 2
site(2).waitForClusterToForm(CacheType.BACKUP_TO_SITE_1_AND_2.name());
site(2).waitForClusterToForm(CacheType.BACKUP_TO_SITE_2.name());
}
private void awaitXSiteStateTransfer() {
awaitXSiteStateTransferFor(null);
awaitXSiteStateTransferFor(CacheType.BACKUP_TO_SITE_1_AND_2.name());
awaitXSiteStateTransferFor(CacheType.BACKUP_TO_SITE_2.name());
}
private void awaitXSiteStateTransferFor(String cacheName) {
eventually(format("Failed to complete the x-site state transfer for cache '%s'", cacheName),
() -> xSiteAdminOperations(0, 0, cacheName).getRunningStateTransfer().isEmpty());
}
private void setSitesStatus(boolean online) {
if (online) {
xSiteAdminOperations(0, 0, null).bringSiteOnline(siteName(1));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name()).bringSiteOnline(siteName(1));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name()).bringSiteOnline(siteName(2));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_2.name()).bringSiteOnline(siteName(2));
} else {
xSiteAdminOperations(0, 0, null).takeSiteOffline(siteName(1));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name()).takeSiteOffline(siteName(1));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name()).takeSiteOffline(siteName(2));
xSiteAdminOperations(0, 0, CacheType.BACKUP_TO_SITE_2.name()).takeSiteOffline(siteName(2));
}
}
private void putInAllCache(String key, String value) {
cache(0, 0, null).put(key, value);
cache(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name()).put(key, value);
cache(0, 0, CacheType.BACKUP_TO_SITE_2.name()).put(key, value);
cache(0, 0, CacheType.NO_BACKUP.name()).put(key, value);
}
private void assertValueInAllCachesInPrimarySite(String key, String value) {
assertValueInCache(0, null, key, value);
assertValueInCache(0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), key, value);
assertValueInCache(0, CacheType.BACKUP_TO_SITE_2.name(), key, value);
assertValueInCache(0, CacheType.NO_BACKUP.name(), key, value);
}
private void assertValueInCache(int siteIndex, String cacheName, String key, String value) {
for (int nodeIndex = 0; nodeIndex < defaultNumberOfNodes(); nodeIndex++) {
assertEquals(format("Wrong value for key '%s' in cache '%s' on site '%d' and node '%d'",
key, cacheName, siteIndex, nodeIndex),
value, cache(siteIndex, nodeIndex, cacheName).get(key));
}
}
private XSiteAdminOperations xSiteAdminOperations(int siteIndex, int nodeIndex, String cacheName) {
return TestingUtil.extractComponent(cache(siteIndex, nodeIndex, cacheName), XSiteAdminOperations.class);
}
private <K, V> Cache<K, V> cache(int siteIndex, int nodeIndex, String cacheName) {
if (cacheName == null) {
return site(siteIndex).cache(nodeIndex);
} else {
return site(siteIndex).cache(cacheName, nodeIndex);
}
}
private void assertCacheEmpty(int siteIndex, int nodeIndex, String cacheName) {
assertTrue(format("Cache '%s' is not empty in site '%d'", cacheName, siteIndex),
cache(siteIndex, nodeIndex, cacheName).isEmpty());
}
private void assertAllCachesEmpty() {
for (CacheType cacheType : CacheType.values()) {
assertCacheEmpty(0, 0, cacheType.name());
}
assertCacheEmpty(1, 0, null);
assertCacheEmpty(1, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name());
assertCacheEmpty(2, 0, CacheType.BACKUP_TO_SITE_2.name());
}
private void assertSiteStatusInAllCaches(String status) {
assertSiteStatus(0, 0, null, 1, status);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 1, status);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_1_AND_2.name(), 2, status);
assertSiteStatus(0, 0, CacheType.BACKUP_TO_SITE_2.name(), 2, status);
}
private void assertSiteStatus(int siteIndex, int nodeIndex, String cacheName, int backupSiteIndex, String status) {
assertEquals(format("Wrong site status for cache '%s' in site '%d' for backup site '%d'.", cacheName, siteIndex, backupSiteIndex),
status,
xSiteAdminOperations(siteIndex, nodeIndex, cacheName).siteStatus(siteName(backupSiteIndex)));
}
private List<BlockingTransport> getBlockingTransport(int siteIndex, boolean createIfAbsent) {
List<EmbeddedCacheManager> cacheManagerList = site(siteIndex).cacheManagers();
List<BlockingTransport> blockingTransportList = new ArrayList<>(cacheManagerList.size());
cacheManagerList.forEach(cacheManager -> {
Transport transport = cacheManager.getTransport();
if (transport instanceof BlockingTransport) {
blockingTransportList.add((BlockingTransport) transport);
} else if (createIfAbsent) {
BlockingTransport blockingTransport = new BlockingTransport(transport);
cacheManager.getGlobalComponentRegistry().registerComponent(blockingTransport, Transport.class);
cacheManager.getGlobalComponentRegistry().rewire();
cacheManager.getGlobalComponentRegistry().rewireNamedRegistries();
blockingTransportList.add(blockingTransport);
}
});
return blockingTransportList.isEmpty() ? Collections.emptyList() : blockingTransportList;
}
protected enum CacheType {
BACKUP_TO_SITE_2,
BACKUP_TO_SITE_1_AND_2,
NO_BACKUP
}
private static class BlockingTransport extends AbstractDelegatingTransport {
private final NotifierLatch notifierLatch;
public BlockingTransport(Transport actual) {
super(actual);
notifierLatch = new NotifierLatch();
notifierLatch.stopBlocking();
}
public void blockCommands() {
notifierLatch.startBlocking();
}
public void unblockCommands() {
notifierLatch.stopBlocking();
}
@Override
public void start() {
//skip start it again.
}
@Override
protected void beforeBackupRemotely(XSiteReplicateCommand command) {
if (command instanceof XSiteStatePushCommand) {
notifierLatch.blockIfNeeded();
}
}
}
}