/*
* 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;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import static org.apache.geode.test.dunit.Assert.*;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.DiskAccessException;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.cache.client.internal.PoolImpl;
import org.apache.geode.cache.server.CacheServer;
import org.apache.geode.cache30.CacheSerializableRunnable;
import org.apache.geode.internal.AvailablePort;
import org.apache.geode.internal.cache.persistence.UninterruptibleFileChannel;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.NetworkUtils;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;
/**
* Tests that if a node doing GII experiences DiskAccessException, it should also not try to recover
* from the disk
*/
@Category(DistributedTest.class)
public class Bug39079DUnitTest extends JUnit4CacheTestCase {
private static final String REGION_NAME_testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase =
"IGNORE_EXCEPTION_testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase";
private static final String REGION_NAME_testGIIDiskAccessException =
"IGNORE_EXCEPTION_testGIIDiskAccessException";
private VM vm0;
private VM vm1;
@Override
public final void postSetUp() throws Exception {
disconnectAllFromDS();
final Host host = Host.getHost(0);
vm0 = host.getVM(0);
vm1 = host.getVM(1);
vm0.invoke(() -> ignorePreAllocate(true));
vm1.invoke(() -> ignorePreAllocate(true));
}
@Override
public final void postTearDownCacheTestCase() throws Exception {
disconnectAllFromDS();
vm0.invoke(() -> ignorePreAllocate(false));
vm1.invoke(() -> ignorePreAllocate(false));
}
/**
* If the node experiences disk access exception during GII, it should get destroyed & not attempt
* to recover from the disk
*/
@Test
public void testGIIDiskAccessException() throws Exception {
vm0.invoke(createCacheForVM0());
vm1.invoke(createCacheForVM1());
// Create DiskRegion locally in controller VM also
getSystem();
assertTrue(getCache() != null);
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
factory.setDiskSynchronous(false);
factory.setDiskStoreName(getCache().createDiskStoreFactory().setDiskDirs(getDiskDirs())
.create(getClass().getSimpleName()).getName());
RegionAttributes attr = factory.create();
Region region = getCache().createRegion(REGION_NAME_testGIIDiskAccessException, attr);
// Now put entries in the disk region
for (int i = 0; i < 100; ++i) {
region.put(new Integer(i), new Integer(i));
}
// Now close the region in the controller VM
region.close();
// Now recreate the region but set the factory such that disk region entry object
// used is customized by us to throw exception while writing to disk
DistributedRegion distRegion =
new DistributedRegion(REGION_NAME_testGIIDiskAccessException, attr, null,
(GemFireCacheImpl) getCache(), new InternalRegionArguments().setDestroyLockFlag(true)
.setRecreateFlag(false).setSnapshotInputStream(null).setImageTarget(null));
distRegion.entries.setEntryFactory(TestAbstractDiskRegionEntry.getEntryFactory());
region = null;
try {
region =
((GemFireCacheImpl) getCache()).createVMRegion(REGION_NAME_testGIIDiskAccessException,
attr, new InternalRegionArguments().setInternalMetaRegion(distRegion)
.setDestroyLockFlag(true).setSnapshotInputStream(null).setImageTarget(null));
fail("Expected DiskAccessException");
} catch (DiskAccessException expected) {
}
assertTrue(region == null || region.isDestroyed()); // TODO: why is this an OR instead of
// deterministic?
}
/**
* If IOException occurs while updating an entry in an already initialized DiskRegion ,then the
* bridge servers should be stopped , if any running
*/
@Test
public void testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase() throws Exception {
// create server cache
Integer port = vm0.invoke(() -> createServerCache());
// create cache client
vm1.invoke(() -> createClientCache(NetworkUtils.getServerHostName(vm0.getHost()), port));
// validate
vm0.invoke(() -> validateRunningBridgeServerList());
// close server cache
vm0.invoke(() -> closeCacheAndDisconnect());
// close client cache
vm1.invoke(() -> closeCacheAndDisconnect());
}
private int createServerCache() throws IOException {
createCache(new Properties());
DiskRegionProperties props = new DiskRegionProperties();
props.setRegionName(REGION_NAME_testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase);
props.setOverflow(true);
props.setRolling(true);
props.setDiskDirs(getDiskDirs());
props.setPersistBackup(true);
Region region =
DiskRegionHelperFactory.getSyncPersistOnlyRegion(getCache(), props, Scope.DISTRIBUTED_ACK);
assertNotNull(region);
CacheServer bs1 = getCache().addCacheServer();
int port = AvailablePort.getRandomAvailablePort(AvailablePort.SOCKET);
bs1.setPort(port);
bs1.start();
return bs1.getPort();
}
private void closeCacheAndDisconnect() {
closeCache();
disconnectFromDS();
}
private void createCache(Properties props) {
getSystem(props);
assertNotNull(getCache());
}
private void validateRunningBridgeServerList() throws IOException {
Region region = getCache()
.getRegion(REGION_NAME_testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase);
try {
region.create("key1", new byte[16]);
region.create("key2", new byte[16]);
// Get the oplog handle & hence the underlying file & close it
UninterruptibleFileChannel oplogFileChannel =
((LocalRegion) region).getDiskRegion().testHook_getChild().getFileChannel();
try {
oplogFileChannel.close();
region.put("key2", new byte[16]);
fail("Expected DiskAccessException");
} catch (DiskAccessException expected) {
}
((LocalRegion) region).getDiskStore().waitForClose();
assertTrue(region.getRegionService().isClosed());
region = null;
List bsRunning = getCache().getCacheServers();
assertTrue(bsRunning.isEmpty());
} finally {
if (region != null) {
region.destroyRegion();
}
}
}
private void createClientCache(String host, Integer port1) {
Properties props = new Properties();
props.setProperty(MCAST_PORT, "0");
props.setProperty(LOCATORS, "");
createCache(props);
PoolImpl pool = (PoolImpl) PoolManager.createFactory().addServer(host, port1.intValue())
.setSubscriptionEnabled(true).setSubscriptionRedundancy(0).setThreadLocalConnections(true)
.setMinConnections(0).setReadTimeout(20000).setRetryAttempts(1)
.create(getClass().getSimpleName());
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setPoolName(pool.getName());
RegionAttributes attrs = factory.create();
Region region = getCache().createRegion(
REGION_NAME_testBridgeServerStoppingInSynchPersistOnlyForIOExceptionCase, attrs);
region.registerInterest("ALL_KEYS");
}
/**
* This method is used to create Cache in VM0
*/
private CacheSerializableRunnable createCacheForVM0() {
return new CacheSerializableRunnable("createCache") {
@Override
public void run2() {
try {
getSystem();
assertNotNull(getCache());
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
factory.setDiskSynchronous(false);
factory.setDiskStoreName(getCache().createDiskStoreFactory().setDiskDirs(getDiskDirs())
.create(getClass().getSimpleName()).getName());
RegionAttributes attr = factory.create();
getCache().createRegion(REGION_NAME_testGIIDiskAccessException, attr);
} catch (Exception ex) {
fail("Error Creating cache / region ", ex);
}
}
};
}
/**
* This method is used to create Cache in VM1
*/
private CacheSerializableRunnable createCacheForVM1() {
return new CacheSerializableRunnable("createCache") {
@Override
public void run2() {
try {
getSystem();
assertNotNull(getCache());
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
factory.setDiskSynchronous(false);
factory.setDiskStoreName(getCache().createDiskStoreFactory().setDiskDirs(getDiskDirs())
.create(getClass().getSimpleName()).getName());
RegionAttributes attr = factory.create();
getCache().createRegion(REGION_NAME_testGIIDiskAccessException, attr);
} catch (Exception ex) {
fail("Error Creating cache / region ", ex);
}
}
};
}
private void ignorePreAllocate(boolean flag) throws Exception {
DiskStoreImpl.SET_IGNORE_PREALLOCATE = flag;
}
private static class TestAbstractDiskRegionEntry extends VMThinDiskRegionEntryHeapObjectKey {
protected TestAbstractDiskRegionEntry(RegionEntryContext r, Object key, Object value) {
super(r, key, value);
}
private static RegionEntryFactory factory = new RegionEntryFactory() {
@Override
public final RegionEntry createEntry(RegionEntryContext r, Object key, Object value) {
throw new DiskAccessException(new IOException("Test Exception"));
}
@Override
public final Class getEntryClass() {
return getClass();
}
@Override
public RegionEntryFactory makeVersioned() {
return this;
}
@Override
public RegionEntryFactory makeOnHeap() {
return this;
}
};
/**
* Overridden setValue method to throw exception
*/
@Override
protected void setValueField(Object v) {
throw new DiskAccessException(new IOException("Test Exception"));
}
public static RegionEntryFactory getEntryFactory() {
return factory;
}
}
}