/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.internal.cache.ha;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import static org.junit.Assert.*;
import java.util.Properties;
import org.apache.geode.test.junit.categories.ClientSubscriptionTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.client.ClientCacheFactory;
import org.apache.geode.cache.client.ClientRegionFactory;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.internal.AvailablePort;
import org.apache.geode.internal.OSProcess;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.tier.sockets.CacheClientNotifier;
import org.apache.geode.internal.cache.tier.sockets.CacheClientProxy;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;
@Category({DistributedTest.class, ClientSubscriptionTest.class})
@SuppressWarnings("serial")
public class Bug48879DUnitTest extends JUnit4DistributedTestCase {
private static VM vm0 = null;
private static VM vm1 = null;
private static GemFireCacheImpl cache;
public static final String REGION_NAME = "Bug48879DUnitTest_region";
public static final int SLEEP_TIME = 40000;
public Bug48879DUnitTest() {
super();
}
@Override
public final void postSetUp() throws Exception {
disconnectAllFromDS();
Host host = Host.getHost(0);
vm0 = host.getVM(0); // server1
vm1 = host.getVM(1); // server2
int port0 = (Integer) vm0.invoke(() -> Bug48879DUnitTest.createCacheServer());
int port1 = (Integer) vm1.invoke(() -> Bug48879DUnitTest.createCacheServer());
createClientCache(host, new Integer[] {port0, port1}, Boolean.TRUE);
}
@Override
public final void preTearDown() throws Exception {
closeCache();
vm0.invoke(() -> Bug48879DUnitTest.closeCache());
vm1.invoke(() -> Bug48879DUnitTest.closeCache());
}
public static void closeCache() throws Exception {
HARegionQueue.threadIdExpiryTime = 300;
System.setProperty(DistributionConfig.GEMFIRE_PREFIX + "MessageTimeToLive", "180");
if (cache != null) {
cache.close();
}
}
@SuppressWarnings({"unused", "deprecation"})
public static Integer createCacheServer() throws Exception {
Bug48879DUnitTest test = new Bug48879DUnitTest();
System.setProperty(DistributionConfig.GEMFIRE_PREFIX + "MessageTimeToLive", "30");
cache = (GemFireCacheImpl) CacheFactory.create(test.getSystem());
HARegionQueue.threadIdExpiryTime = (SLEEP_TIME / 1000) - 10;
cache.setMessageSyncInterval(SLEEP_TIME / 500);
RegionFactory<String, String> rf = cache.createRegionFactory(RegionShortcut.REPLICATE);
Region<String, String> region = rf.create(REGION_NAME);
CacheServer server = cache.addCacheServer();
server.setPort(AvailablePort.getRandomAvailablePort(AvailablePort.SOCKET));
server.start();
return server.getPort();
}
@SuppressWarnings("deprecation")
public static void createClientCache(Host host, Integer[] ports, Boolean doRI) throws Exception {
Properties props = new Properties();
props.setProperty(STATISTIC_ARCHIVE_FILE, "client_" + OSProcess.getId() + ".gfs");
props.setProperty(STATISTIC_SAMPLING_ENABLED, "true");
DistributedSystem ds = new Bug48879DUnitTest().getSystem(props);
ds.disconnect();
ClientCacheFactory ccf = new ClientCacheFactory(props);
ccf.setPoolSubscriptionEnabled(doRI);
ccf.setPoolSubscriptionAckInterval(50);
ccf.setPoolSubscriptionRedundancy(1);
for (int port : ports) {
ccf.addPoolServer(host.getHostName(), port);
}
cache = (GemFireCacheImpl) ccf.create();
ClientRegionFactory<String, String> crf =
cache.createClientRegionFactory(ClientRegionShortcut.CACHING_PROXY);
Region<String, String> region = crf.create(REGION_NAME);
if (doRI) {
region.registerInterest("ALL_KEYS");
}
}
@SuppressWarnings({"unused", "unchecked"})
public static void doPuts(Integer numOfThreads, Integer puts) throws Exception {
Region<String, String> region = cache.getRegion(REGION_NAME);
final int putsPerThread = puts;
Thread[] threads = new Thread[numOfThreads];
for (int i = 0; i < numOfThreads; i++) {
final String threadId = "Thread_" + i + "X";
threads[i] = new Thread(new Runnable() {
@SuppressWarnings("rawtypes")
public void run() {
Region region = cache.getRegion(REGION_NAME);
for (int i = 0; i < putsPerThread; i++) {
region.put(threadId + i, "VALUE_" + i);
}
}
});
threads[i].start();
}
for (int i = 0; i < numOfThreads; i++) {
try {
threads[i].join();
} catch (InterruptedException ie) {
}
}
Thread.sleep(5000);
region.put("LAST", "LAST");
}
public static Boolean isPrimaryServer() {
return ((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0])
.isPrimary();
}
public static void verifyStats(Integer numOfEvents, Integer expectedTids) throws Exception {
HARegionQueueStats stats =
((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0])
.getHARegionQueue().getStatistics();
long actualExpiry = stats.getEventsExpired();
long expectedExpiry = isPrimaryServer() ? 0 : numOfEvents + 1; // +1 for LAST key
assertEquals(
"Expected eventsExpired: " + expectedExpiry + " but actual eventsExpired: " + actualExpiry
+ (isPrimaryServer() ? " at primary." : " at secondary."),
expectedExpiry, actualExpiry);
int actualTids = stats.getThreadIdentiferCount();
assertTrue("Expected ThreadIdentifier count <= 1 but actual: " + actualTids
+ (isPrimaryServer() ? " at primary." : " at secondary."), actualTids <= 1); // Sometimes we
// may see 1
// threadIdentifier
// due to slow
// machines,
// but never
// equal to
// expectedTids
}
public static void verifyThreadsBeforeExpiry(Integer expectedTids) throws Exception {
HARegionQueueStats stats =
((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0])
.getHARegionQueue().getStatistics();
int actualTids = stats.getThreadIdentiferCount();
assertTrue("Expected ThreadIdentifier count >= " + expectedTids + " but actual: " + actualTids
+ (isPrimaryServer() ? " at primary." : " at secondary."), actualTids >= expectedTids);
}
@Test
public void testThreadIdentfiersExpiry() throws Exception {
// create server1 and server2
// create client with redundancy = 1
// put events in region
int threads = 10;
int putsPerThread = 1;
vm0.invoke(() -> Bug48879DUnitTest.doPuts(threads, putsPerThread));
vm0.invoke(() -> Bug48879DUnitTest.verifyThreadsBeforeExpiry(threads));
vm1.invoke(() -> Bug48879DUnitTest.verifyThreadsBeforeExpiry(threads));
// sleep till expiry time elapses
Thread.sleep(SLEEP_TIME * 2 + 30000);
// Assert that threadidentifiers are expired and region events are retained on primary server
vm0.invoke(() -> Bug48879DUnitTest.verifyStats(threads * putsPerThread, threads));
// Assert that region events and threadidentifiers are expired on secondary server.
vm1.invoke(() -> Bug48879DUnitTest.verifyStats(threads * putsPerThread, threads));
}
}