/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.internal.nearcache;
import com.hazelcast.config.EvictionConfig;
import com.hazelcast.config.EvictionConfig.MaxSizePolicy;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.InMemoryFormat;
import com.hazelcast.config.NearCacheConfig;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.monitor.NearCacheStats;
import com.hazelcast.test.AssertTask;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.SlowTest;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import java.io.File;
import static com.hazelcast.internal.nearcache.NearCacheTestUtils.assertNearCacheSizeEventually;
import static com.hazelcast.internal.nearcache.NearCacheTestUtils.createNearCacheConfig;
import static com.hazelcast.nio.IOUtil.deleteQuietly;
import static com.hazelcast.nio.IOUtil.getFileFromResources;
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Contains the logic code for unified Near Cache preloader tests.
*
* @param <NK> key type of the tested Near Cache
* @param <NV> value type of the tested Near Cache
*/
public abstract class AbstractNearCachePreloaderTest<NK, NV> extends HazelcastTestSupport {
/**
* The type of the key of the used {@link com.hazelcast.internal.adapter.DataStructureAdapter}.
*/
public enum KeyType {
INTEGER,
STRING,
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
protected static final int TEST_TIMEOUT = 120000;
protected static final int KEY_COUNT = 10023;
protected final String defaultNearCache = randomName();
private final File preloadDir10kInt = getFileFromResources("nearcache-10k-int");
private final File preloadDir10kString = getFileFromResources("nearcache-10k-string");
private final File preloadDirEmpty = getFileFromResources("nearcache-empty");
private final File preloadDirInvalidMagicBytes = getFileFromResources("nearcache-invalid-magicbytes");
private final File preloadDirInvalidFileFormat = getFileFromResources("nearcache-invalid-fileformat");
private final File preloadDirNegativeFileFormat = getFileFromResources("nearcache-negative-fileformat");
@After
public void deleteFiles() {
deleteQuietly(getStoreFile());
deleteQuietly(getStoreLockFile());
}
/**
* The {@link NearCacheConfig} used by the Near Cache tests.
*
* Needs to be set by the implementations of this class in their {@link org.junit.Before} methods.
*/
protected NearCacheConfig nearCacheConfig;
/**
* Creates the {@link NearCacheTestContext} used by the Near Cache tests.
*
* @param <K> key type of the created {@link com.hazelcast.internal.adapter.DataStructureAdapter}
* @param <V> value type of the created {@link com.hazelcast.internal.adapter.DataStructureAdapter}
* @return a {@link NearCacheTestContext} used by the Near Cache tests
*/
protected abstract <K, V> NearCacheTestContext<K, V, NK, NV> createContext(boolean createClient);
/**
* Creates the {@link NearCacheTestContext} with only a client used by the Near Cache tests.
*
* @param <K> key type of the created {@link com.hazelcast.internal.adapter.DataStructureAdapter}
* @param <V> value type of the created {@link com.hazelcast.internal.adapter.DataStructureAdapter}
* @return a {@link NearCacheTestContext} with only a client used by the Near Cache tests
*/
protected abstract <K, V> NearCacheTestContext<K, V, NK, NV> createClientContext();
/**
* Returns the default store file used for the current implementation (IMap or JCache)
*
* @return the default store file
*/
protected abstract File getStoreFile();
/**
* Returns the default store lock file used for the current implementation (IMap or JCache)
*
* @return the default store lock file
*/
protected abstract File getStoreLockFile();
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testStoreAndLoad_withIntegerKeys_withInMemoryFormatBinary() {
storeAndLoad(2342, KeyType.INTEGER, InMemoryFormat.BINARY);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testStoreAndLoad_withIntegerKeys_withInMemoryFormatObject() {
storeAndLoad(2342, KeyType.INTEGER, InMemoryFormat.OBJECT);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testStoreAndLoad_withStringKeys_withInMemoryFormatBinary() {
storeAndLoad(4223, KeyType.STRING, InMemoryFormat.BINARY);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testStoreAndLoad_withStringKeys_withInMemoryFormatObject() {
storeAndLoad(4223, KeyType.STRING, InMemoryFormat.OBJECT);
}
@SuppressWarnings("WeakerAccess")
protected void storeAndLoad(int keyCount, KeyType keyType, InMemoryFormat inMemoryFormat) {
nearCacheConfig.setInMemoryFormat(inMemoryFormat);
nearCacheConfig.getPreloaderConfig()
.setStoreInitialDelaySeconds(3)
.setStoreIntervalSeconds(1);
NearCacheTestContext<Object, String, NK, NV> context = createContext(true);
populateNearCache(context, keyCount, keyType);
waitForNearCachePersistence(context, 1);
assertLastNearCachePersistence(context, getStoreFile(), keyCount);
// shutdown the first client
context.nearCacheInstance.shutdown();
// start a new client which will kick off the Near Cache pre-loader
NearCacheTestContext<Object, String, NK, NV> clientContext = createClientContext();
// wait until the pre-loading is done, then check for the Near Cache size
assertNearCachePreloadDoneEventually(clientContext);
assertNearCacheSizeEventually(clientContext, keyCount);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testCreateStoreFile_withInvalidDirectory() {
String directory = "/dev/null/";
nearCacheConfig.getPreloaderConfig()
.setStoreInitialDelaySeconds(1)
.setStoreIntervalSeconds(1)
.setDirectory(directory);
File lockFile = new File(directory, getStoreFile().getName());
expectedException.expectMessage("Cannot create lock file " + lockFile.getAbsolutePath());
expectedException.expect(HazelcastException.class);
createContext(true);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testCreateStoreFile_withStringKey() {
createStoreFile(KEY_COUNT, KeyType.STRING);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testCreateStoreFile_withIntegerKey() {
createStoreFile(KEY_COUNT, KeyType.INTEGER);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testCreateStoreFile_withEmptyNearCache() {
createStoreFile(0, KeyType.INTEGER);
}
private void createStoreFile(int keyCount, KeyType keyType) {
nearCacheConfig.getPreloaderConfig()
.setStoreInitialDelaySeconds(2)
.setStoreIntervalSeconds(1);
NearCacheTestContext<Object, String, NK, NV> context = createContext(true);
populateNearCache(context, keyCount, keyType);
waitForNearCachePersistence(context, 3);
assertLastNearCachePersistence(context, getStoreFile(), keyCount);
}
@Test(timeout = TEST_TIMEOUT)
@Category(SlowTest.class)
public void testCreateStoreFile_whenTwoClientsWithSameStoreFile_thenThrowException() {
nearCacheConfig.getPreloaderConfig()
.setStoreInitialDelaySeconds(2)
.setStoreIntervalSeconds(1);
// the first client creates the lock file and holds the lock on it
NearCacheTestContext<Object, String, NK, NV> context = createContext(true);
// assure that the pre-loader is working on the first client
populateNearCache(context, KEY_COUNT, KeyType.INTEGER);
waitForNearCachePersistence(context, 3);
assertLastNearCachePersistence(context, getStoreFile(), KEY_COUNT);
// the second client cannot acquire the lock, so it fails with an exception
expectedException.expectMessage("Cannot acquire lock on " + getStoreFile());
expectedException.expect(HazelcastException.class);
createClientContext();
}
@Test
public void testPreloadNearCache_withIntegerKeys() {
preloadNearCache(preloadDir10kInt, 10000, false);
}
@Test
public void testPreloadNearCache_withStringKeys() {
preloadNearCache(preloadDir10kString, 10000, true);
}
@Test
public void testPreloadNearCache_withEmptyFile() {
preloadNearCache(preloadDirEmpty, 0, false);
}
@Test
public void testPreloadNearCache_withInvalidMagicBytes() {
preloadNearCache(preloadDirInvalidMagicBytes, 0, false);
}
@Test
public void testPreloadNearCache_withInvalidFileFormat() {
preloadNearCache(preloadDirInvalidFileFormat, 0, false);
}
@Test
public void testPreloadNearCache_withNegativeFileFormat() {
preloadNearCache(preloadDirNegativeFileFormat, 0, false);
}
private void preloadNearCache(File preloaderDir, int keyCount, boolean useStringKey) {
nearCacheConfig
.setName("defaultNearCache")
.getPreloaderConfig().setDirectory(preloaderDir.getAbsolutePath());
NearCacheTestContext<Object, String, NK, NV> context = createContext(false);
// populate the member side data structure, so we have the values to populate the client side Near Cache
for (int i = 0; i < keyCount; i++) {
Object key = useStringKey ? "key-" + i : i;
context.dataAdapter.put(key, "value-" + i);
}
// start the client which will kick off the Near Cache pre-loader
NearCacheTestContext<Object, String, NK, NV> clientContext = createClientContext();
// wait until the pre-loading is done, then check for the Near Cache size
assertNearCachePreloadDoneEventually(clientContext);
assertNearCacheSizeEventually(clientContext, keyCount);
}
protected NearCacheConfig getNearCacheConfig(boolean invalidationOnChange, int maxSize, String preloaderDir) {
EvictionConfig evictionConfig = new EvictionConfig()
.setMaximumSizePolicy(MaxSizePolicy.ENTRY_COUNT)
.setSize(maxSize)
.setEvictionPolicy(EvictionPolicy.LRU);
NearCacheConfig nearCacheConfig = createNearCacheConfig(InMemoryFormat.BINARY)
.setName(defaultNearCache)
.setInvalidateOnChange(invalidationOnChange)
.setEvictionConfig(evictionConfig);
nearCacheConfig.getPreloaderConfig()
.setEnabled(true)
.setDirectory(preloaderDir);
return nearCacheConfig;
}
private void populateNearCache(NearCacheTestContext<Object, String, NK, NV> context, int keyCount, KeyType keyType) {
for (int i = 0; i < keyCount; i++) {
Object key = createKey(keyType, i);
context.nearCacheAdapter.put(key, "value-" + i);
context.nearCacheAdapter.get(key);
}
}
private static Object createKey(KeyType keyType, int i) {
switch (keyType) {
case STRING:
return "key-" + i;
case INTEGER:
return i;
default:
throw new IllegalArgumentException("Unknown keyType: " + keyType);
}
}
private static long getPersistenceCount(NearCacheTestContext context) {
return context.nearCache.getNearCacheStats().getPersistenceCount();
}
private static void waitForNearCachePersistence(final NearCacheTestContext context, final int persistenceCount) {
final long oldPersistenceCount = getPersistenceCount(context);
assertTrueEventually(new AssertTask() {
@Override
public void run() {
long newPersistenceCount = getPersistenceCount(context);
assertTrue(format("We saw %d persistences before and were waiting for %d new persistences, but still got %d",
oldPersistenceCount, persistenceCount, newPersistenceCount),
newPersistenceCount > oldPersistenceCount + persistenceCount);
}
});
}
private static void assertLastNearCachePersistence(NearCacheTestContext context, File defaultStoreFile, int keyCount) {
NearCacheStats nearCacheStats = context.nearCache.getNearCacheStats();
assertEquals(keyCount, nearCacheStats.getLastPersistenceKeyCount());
if (keyCount > 0) {
assertTrue(nearCacheStats.getLastPersistenceWrittenBytes() > 0);
assertTrue(defaultStoreFile.exists());
} else {
assertEquals(0, nearCacheStats.getLastPersistenceWrittenBytes());
assertFalse(defaultStoreFile.exists());
}
assertTrue(nearCacheStats.getLastPersistenceFailure().isEmpty());
}
private static void assertNearCachePreloadDoneEventually(final NearCacheTestContext clientContext) {
assertTrueEventually(new AssertTask() {
@Override
public void run() {
assertTrue(clientContext.nearCache.isPreloadDone());
}
});
}
}