/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.catalog.cache;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import javax.activation.MimeType;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryEventType;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.test.TestHazelcastInstanceFactory;
import ddf.catalog.cache.impl.ProductCacheDirListener;
import ddf.catalog.cache.impl.ResourceCache;
import ddf.catalog.data.impl.MetacardImpl;
import ddf.catalog.resource.data.ReliableResource;
public class ResourceCacheSizeLimitTest {
private static final String PRODUCT_CACHE_NAME = "Product_Cache";
private static final transient Logger LOGGER = LoggerFactory
.getLogger(ResourceCacheSizeLimitTest.class);
private static TestHazelcastInstanceFactory hcInstanceFactory;
private static String productCacheDir;
private static ProductCacheDirListener<Object, Object> listener;
@BeforeClass
public static void oneTimeSetup() {
String workingDir = System.getProperty("user.dir") + File.separator + "target";
System.setProperty("karaf.home", workingDir);
productCacheDir =
workingDir + File.separator + ResourceCache.DEFAULT_PRODUCT_CACHE_DIRECTORY;
hcInstanceFactory = new TestHazelcastInstanceFactory(10);
listener = new ProductCacheDirListener<Object, Object>(15);
}
@AfterClass
public static void oneTimeTeardown() {
LOGGER.debug("instances still remaining" + Hazelcast.getAllHazelcastInstances().size());
}
@After
public void teardownTest() throws IOException {
Collection<HazelcastInstance> instances = hcInstanceFactory.getAllHazelcastInstances();
HazelcastInstance instance = instances.iterator().next();
instance.shutdown();
cleanProductCacheDirectory();
}
@Test
//@Ignore
public void testExceedCacheDirMaxSize() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setMaxDirSizeBytes(15);
listener.setHazelcastInstance(instance);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
// Simulate adding product to product cache
String rr1Key = "rr1";
String rr1FileName = "10bytes.txt";
simulateAddFileToProductCache(rr1Key, rr1FileName, rr1FileName,
cacheMap); //this may take longer to execute than just the next line.
// ensure that the entry has not been removed from the cache since it doesn't exceed the max size
verifyCached(cacheMap, rr1Key, rr1FileName);
// simulate adding additional product to cache
String rr2Key = "rr2";
String rr2FileName = "15bytes.txt";
simulateAddFileToProductCache(rr2Key, rr2FileName, rr2FileName, cacheMap);
verifyRemovedFromCache(cacheMap, rr1Key, rr1FileName);
verifyCached(cacheMap, rr2Key, rr2FileName);
}
@Test
public void testExceedCacheDirMaxSizeMultipleEvictions()
throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setMaxDirSizeBytes(28);
listener.setHazelcastInstance(instance);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
//Simulate adding product to product cache
String rr1Key = "rr1";
String rr1FileName = "10bytes.txt";
simulateAddFileToProductCache(rr1Key, rr1FileName, rr1FileName, cacheMap);
//ensure that the entry has not been removed from the cache since it doesn't exceed the max size
ReliableResource rrFromCache = (ReliableResource) cacheMap.get(rr1Key);
assertNotNull(rrFromCache);
//simulate adding additional product to cache
String rr2Key = "rr2";
String rr2FileName = "15bytes.txt";
simulateAddFileToProductCache(rr2Key, rr2FileName, rr2FileName, cacheMap);
//simulate adding additional product to cache
String rr3Key = "rr3";
String rr3FileName = "15bytes_B.txt";
simulateAddFileToProductCache(rr3Key, rr3FileName, rr3FileName, cacheMap);
verifyRemovedFromCache(cacheMap, rr1Key, rr1FileName);
verifyRemovedFromCache(cacheMap, rr2Key, rr2FileName);
verifyCached(cacheMap, rr3Key, rr3FileName);
}
@Test
//@Ignore
public void testNotExceedCacheDirMaxSize() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setMaxDirSizeBytes(50);
listener.setHazelcastInstance(instance);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
//Simulate adding product to product cache
String rr1Key = "rr1";
String rr1FileName = "10bytes.txt";
simulateAddFileToProductCache(rr1Key, rr1FileName, rr1FileName, cacheMap);
//simulate adding additional product to cache
String rr2Key = "rr2";
String rr2FileName = "15bytes.txt";
simulateAddFileToProductCache(rr2Key, rr2FileName, rr2FileName, cacheMap);
//simulate adding additional product to cache
String rr3Key = "rr3";
String rr3FileName = "15bytes_B.txt";
simulateAddFileToProductCache(rr3Key, rr3FileName, rr3FileName, cacheMap);
verifyCached(cacheMap, rr1Key, rr1FileName);
verifyCached(cacheMap, rr2Key, rr2FileName);
verifyCached(cacheMap, rr3Key, rr3FileName);
}
@Test
public void testSingleFileExceedCacheDirMaxSize() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
listener.setMaxDirSizeBytes(5);
listener.setHazelcastInstance(instance);
//Simulate adding product to product cache
String rr1Key = "rr1";
String rr1FileName = "10bytes.txt";
simulateAddFileToProductCache(rr1Key, rr1FileName, rr1FileName, cacheMap);
verifyRemovedFromCache(cacheMap, rr1Key, rr1FileName);
}
@Test
public void testCacheDirMaxSizeManyEntries() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setHazelcastInstance(instance);
listener.setMaxDirSizeBytes(10);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
//Simulate adding product to product cache
String rrKeyPrefix = "rr";
String rr1FileNameBase = "10bytes.txt";
int indexOfRemainingEntry = 11;
for (int i = 0; i < 11; i++) {
simulateAddFileToProductCache(rrKeyPrefix + i, rr1FileNameBase, i + rr1FileNameBase,
cacheMap);
}
//not in loop in order to slightly delay this file being added to the cache so it is sorted correctly and not accidentally removed
simulateAddFileToProductCache(rrKeyPrefix + 11, rr1FileNameBase, 11 + rr1FileNameBase,
cacheMap);
//entries from 0-10 should be removed from cache
for (int i = 0; i < 11; i++) {
verifyRemovedFromCache(cacheMap, rrKeyPrefix + 1, i + rr1FileNameBase);
}
verifyCached(cacheMap, rrKeyPrefix + indexOfRemainingEntry,
indexOfRemainingEntry + rr1FileNameBase);
}
@Test
public void testCacheDirMaxSizePaging() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setHazelcastInstance(instance);
listener.setMaxDirSizeBytes(132);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
//Simulate adding product to product cache
//push 12 files into cache to total a size of 120 bytes
String rrKeyPrefix = "rr";
String rr1FileNameBase = "10bytes.txt";
for (int i = 0; i < 12; i++) {
simulateAddFileToProductCache(rrKeyPrefix + i, rr1FileNameBase, i + rr1FileNameBase,
cacheMap);
}
//ensure all 12 10-byte files are cached
for (int i = 0; i < 12; i++) {
verifyCached(cacheMap, rrKeyPrefix + i, i + rr1FileNameBase);
}
//push 1 large file into cache to total 245 bytes. This file will be on the second "page" when querying the cache.
String oneTwentyFiveBytesFileName = "125bytes.txt";
int indexOf125Bytes = 12;
simulateAddFileToProductCache(rrKeyPrefix + indexOf125Bytes, oneTwentyFiveBytesFileName,
oneTwentyFiveBytesFileName, cacheMap);
//ensure 125-byte file is cached
verifyCached(cacheMap, rrKeyPrefix + indexOf125Bytes, oneTwentyFiveBytesFileName);
//entries from 0-9 should be removed from cache
for (int i = 0; i < 12; i++) {
verifyRemovedFromCache(cacheMap, rrKeyPrefix + i, i + rr1FileNameBase);
}
verifyCached(cacheMap, rrKeyPrefix + indexOf125Bytes, oneTwentyFiveBytesFileName);
}
@Test
public void testCacheDirMaxSize0() throws IOException, InterruptedException {
HazelcastInstance instance = initializeTestHazelcastInstance();
listener.setMaxDirSizeBytes(0);
listener.setHazelcastInstance(instance);
IMap<String, ReliableResource> cacheMap = instance.getMap(PRODUCT_CACHE_NAME);
//Simulate adding product to product cache
String rr1Key = "rr1";
String rr1FileName = "10bytes.txt";
simulateAddFileToProductCache(rr1Key, rr1FileName, rr1FileName, cacheMap);
//ensure that the entry has not been removed from the cache since it doesn't exceed the max size
verifyCached(cacheMap, rr1Key, rr1FileName);
//simulate adding additional product to cache
String rr2Key = "rr2";
String rr2FileName = "15bytes.txt";
simulateAddFileToProductCache(rr2Key, rr2FileName, rr2FileName, cacheMap);
verifyCached(cacheMap, rr2Key, rr2FileName);
}
private HazelcastInstance initializeTestHazelcastInstance() {
HazelcastInstance instance = hcInstanceFactory.newHazelcastInstance();
IMap<Object, Object> cacheMap1 = instance.getMap(PRODUCT_CACHE_NAME);
// ProductCacheDirListener<Object, Object> listener = new ProductCacheDirListener<Object, Object>(maxDirSizeBytes);
// listener.setHazelcastInstance(instance);
// cacheMap1.addEntryListener(listener, true);
return instance;
}
private ReliableResource simulateAddFileToProductCache(String key, String fileName,
String destFileName, IMap<String, ReliableResource> cacheMap) throws IOException {
String productOriginalLocation =
System.getProperty("user.dir") + "/src/test/resources/" + fileName;
File rrCachedFile = new File(productCacheDir + "/" + destFileName);
FileUtils.copyFile(new File(productOriginalLocation), rrCachedFile);
ReliableResource rr = new ReliableResource(key, rrCachedFile.getAbsolutePath(),
new MimeType(), fileName, new MetacardImpl());
rr.setSize(rrCachedFile.length());
LOGGER.debug("adding entry to cache: " + key);
cacheMap.put(key, rr);
listener.entryAdded(
new EntryEvent<Object, Object>(destFileName, null, EntryEventType.ADDED.getType(),
key, rr));
return rr;
}
private void verifyCached(IMap<String, ReliableResource> cacheMap, String rrKey,
String rrFileName) {
ReliableResource rrFromCache = (ReliableResource) cacheMap.get(rrKey);
assertNotNull(rrFromCache);
File rrCachedFile = new File(productCacheDir + File.separator + rrFileName);
assertTrue(new File(rrCachedFile.getAbsolutePath()).exists());
}
private void verifyRemovedFromCache(IMap<String, ReliableResource> cacheMap, String rrKey,
String rrFileName) {
ReliableResource rrFromCache = (ReliableResource) cacheMap.get(rrKey);
assertNull(rrFromCache);
File rrCachedFile = new File(productCacheDir + File.separator + rrFileName);
assertFalse(new File(rrCachedFile.getAbsolutePath()).exists());
}
private void cleanProductCacheDirectory() throws IOException {
FileUtils.cleanDirectory(new File(productCacheDir));
}
}