/* * 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.partitioned; import static org.apache.geode.distributed.ConfigurationProperties.*; import java.util.Properties; import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.EntryEvent; import org.apache.geode.cache.ExpirationAction; import org.apache.geode.cache.ExpirationAttributes; import org.apache.geode.cache.PartitionAttributesFactory; 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.cache.util.CacheListenerAdapter; import org.apache.geode.distributed.DistributedSystem; import org.apache.geode.internal.AvailablePort; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.cache.ha.HARegionQueueStats; 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.Wait; import org.apache.geode.test.dunit.WaitCriterion; import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase; import org.apache.geode.test.junit.categories.DistributedTest; /** * The test creates two datastores with a partitioned region, and also running a cache server each. * A publisher client is connected to one server while a subscriber client is connected to both the * servers. The partitioned region has entry expiry set with ttl of 3 seconds and action as DESTROY. * The test ensures that the EXPIRE_DESTROY events are propagated to the subscriber client and the * secondary server does process the QRMs for the EXPIRE_DESTROY events. */ @Category(DistributedTest.class) @SuppressWarnings("serial") public class Bug47388DUnitTest extends JUnit4DistributedTestCase { private static VM vm0 = null; private static VM vm1 = null; private static VM vm2 = null; private static VM vm3 = null; private static GemFireCacheImpl cache; private static volatile boolean lastKeyDestroyed = false; public static final String REGION_NAME = Bug47388DUnitTest.class.getSimpleName() + "_region"; @Override public final void postSetUp() throws Exception { disconnectFromDS(); Host host = Host.getHost(0); vm0 = host.getVM(0); // datastore and server vm1 = host.getVM(1); // datastore and server vm2 = host.getVM(2); // durable client with subscription vm3 = host.getVM(3); // durable client without subscription // int mcastPort = AvailablePort.getRandomAvailablePort(AvailablePort.JGROUPS); int port0 = (Integer) vm0.invoke(() -> Bug47388DUnitTest.createCacheServerWithPRDatastore()); int port1 = (Integer) vm1.invoke(() -> Bug47388DUnitTest.createCacheServerWithPRDatastore()); vm2.invoke(Bug47388DUnitTest.class, "createClientCache", new Object[] {vm2.getHost(), new Integer[] {port0, port1}, Boolean.TRUE}); vm3.invoke(Bug47388DUnitTest.class, "createClientCache", new Object[] {vm3.getHost(), new Integer[] {port0}, Boolean.FALSE}); } @Override public final void preTearDown() throws Exception { closeCache(); vm2.invoke(() -> Bug47388DUnitTest.closeCache()); vm3.invoke(() -> Bug47388DUnitTest.closeCache()); vm0.invoke(() -> Bug47388DUnitTest.closeCache()); vm1.invoke(() -> Bug47388DUnitTest.closeCache()); } public static void closeCache() throws Exception { if (cache != null) { cache.close(); } lastKeyDestroyed = false; } @SuppressWarnings("deprecation") public static Integer createCacheServerWithPRDatastore() throws Exception { Properties props = new Properties(); Bug47388DUnitTest test = new Bug47388DUnitTest(); DistributedSystem ds = test.getSystem(props); ds.disconnect(); cache = (GemFireCacheImpl) CacheFactory.create(test.getSystem()); RegionFactory<String, String> rf = cache.createRegionFactory(RegionShortcut.PARTITION); rf.setEntryTimeToLive(new ExpirationAttributes(3, ExpirationAction.DESTROY)) .setPartitionAttributes(new PartitionAttributesFactory<String, String>() .setRedundantCopies(1).setTotalNumBuckets(4).create()) .setConcurrencyChecksEnabled(false); 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(DURABLE_CLIENT_ID, "my-durable-client-" + ports.length); props.setProperty(DURABLE_CLIENT_TIMEOUT, "300000"); DistributedSystem ds = new Bug47388DUnitTest().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); if (doRI) { crf.addCacheListener(new CacheListenerAdapter<String, String>() { @Override public void afterDestroy(EntryEvent<String, String> event) { if (event.getKey().equalsIgnoreCase("LAST_KEY")) { lastKeyDestroyed = true; } } }); } Region<String, String> region = crf.create(REGION_NAME); if (doRI) { region.registerInterest("ALL_KEYS", true); cache.readyForEvents(); } } @SuppressWarnings("unchecked") public static void doPuts(Integer numOfSets, Integer numOfPuts) throws Exception { Region<String, String> region = cache.getRegion(REGION_NAME); for (int i = 0; i < numOfSets; i++) { for (int j = 0; j < numOfPuts; j++) { region.put("KEY_" + i + "_" + j, "VALUE_" + j); } } region.put("LAST_KEY", "LAST_KEY"); } public static Boolean isPrimaryServer() { return ((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0]) .isPrimary(); } public static void verifyClientSubscriptionStats(final Boolean isPrimary, final Integer events) throws Exception { WaitCriterion wc = new WaitCriterion() { private long dispatched; private long qrmed; @Override public boolean done() { HARegionQueueStats stats = ((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0]) .getHARegionQueue().getStatistics(); final int numOfEvents; if (!isPrimary) { numOfEvents = events - 1; // No marker } else { numOfEvents = events; } if (isPrimary) { this.dispatched = stats.getEventsDispatched(); return numOfEvents == this.dispatched; } else { this.qrmed = stats.getEventsRemovedByQrm(); return this.qrmed == numOfEvents || (this.qrmed + 1) == numOfEvents; // Why +1 above? Because sometimes(TODO: explain further) there may // not be any QRM sent to the secondary for the last event dispatched // at primary. } } @Override public String description() { return "Expected events: " + events + " but actual eventsDispatched: " + this.dispatched + " and actual eventsRemovedByQrm: " + this.qrmed; } }; Wait.waitForCriterion(wc, 60 * 1000, 500, true); } public static void waitForLastKeyDestroyed() throws Exception { WaitCriterion wc = new WaitCriterion() { @Override public boolean done() { return lastKeyDestroyed; } @Override public String description() { return "Last key's destroy not received"; } }; Wait.waitForCriterion(wc, 60 * 1000, 500, true); } @Ignore("TODO: test is disabled due to bug51931") @Test public void testQRMOfExpiredEventsProcessedSuccessfully() throws Exception { int numOfSets = 2, numOfPuts = 5; int totalEvents = 23; // = (numOfSets * numOfPuts) * 2 [eviction-destroys] + // 2 [last key's put and eviction-destroy] + 1 [marker // message] vm3.invoke(() -> Bug47388DUnitTest.doPuts(numOfSets, numOfPuts)); boolean isvm0Primary = (Boolean) vm0.invoke(() -> Bug47388DUnitTest.isPrimaryServer()); vm2.invoke(() -> Bug47388DUnitTest.waitForLastKeyDestroyed()); vm0.invoke(() -> Bug47388DUnitTest.verifyClientSubscriptionStats(isvm0Primary, totalEvents)); vm1.invoke(() -> Bug47388DUnitTest.verifyClientSubscriptionStats(!isvm0Primary, totalEvents)); } }