/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.worker.block.evictor;
import alluxio.ConfigurationTestUtils;
import alluxio.worker.block.BlockStoreLocation;
import alluxio.worker.block.TieredBlockStoreTestUtils;
import alluxio.worker.block.meta.StorageDir;
import alluxio.worker.block.meta.StorageTier;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.Reflection;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This is a parametrized unit test for classes that implement the {@link Evictor} interface.
*
* It performs sanity check on evictors regardless of their types, in cases such as not evicting any
* blocks when the required space is already available, proposed eviction ensuring enough space, and
* returning null eviction plan when the requests can not be achieved.
*
* Behavior for a specific type of evictor is tested in other classes, e.g. tests to ensure that
* blocks evicted by {@link LRUEvictor} are in the right order should be in {@link LRUEvictorTest}.
*/
@RunWith(Parameterized.class)
public final class EvictorContractTest extends EvictorTestBase {
private static final int TEST_TIER_LEVEL = 0;
private static final int TEST_DIR = 0;
private StorageDir mTestDir;
private final String mEvictorClassName;
/**
* @return a list of all evictors
*/
@Parameterized.Parameters
public static Collection<Object[]> data() {
// Run this test against all types of Evictors
List<Object[]> list = new ArrayList<>();
try {
String packageName = Reflection.getPackageName(Evictor.class);
ClassPath path = ClassPath.from(Thread.currentThread().getContextClassLoader());
List<ClassPath.ClassInfo> clazzInPackage =
new ArrayList<>(path.getTopLevelClassesRecursive(packageName));
for (ClassPath.ClassInfo clazz : clazzInPackage) {
Set<Class<?>> interfaces =
new HashSet<>(Arrays.asList(clazz.load().getInterfaces()));
if (!Modifier.isAbstract(clazz.load().getModifiers()) && interfaces.size() > 0
&& interfaces.contains(Evictor.class)) {
list.add(new Object[] {clazz.getName()});
}
}
} catch (Exception e) {
Assert.fail("Failed to find implementation of allocate strategy");
}
return list;
}
/**
* @param evictorClassName the class name of the evictor
*/
public EvictorContractTest(String evictorClassName) {
mEvictorClassName = evictorClassName;
}
/**
* Sets up all dependencies before a test runs.
*/
@Before
public final void before() throws Exception {
init(mEvictorClassName);
List<StorageTier> tiers = mMetaManager.getTiers();
mTestDir = tiers.get(TEST_TIER_LEVEL).getDir(TEST_DIR);
}
/**
* Resets the context of the worker after a test ran.
*/
@After
public void after() {
ConfigurationTestUtils.resetConfiguration();
}
/**
* Tests that no eviction plan is created whn there is no cached block in the evictor.
*/
@Test
public void noNeedToEvictTest1() throws Exception {
// metadata manager is just created, no cached block in Evictor,
// so when trying to make sure each dir has free space of its capacity,
// the eviction plan should be empty.
for (StorageTier tier : mMetaManager.getTiers()) {
for (StorageDir dir : tier.getStorageDirs()) {
Assert.assertTrue(mEvictor.freeSpaceWithView(dir.getCapacityBytes(),
dir.toBlockStoreLocation(), mManagerView).isEmpty());
}
}
}
/**
* Tests that no eviction plan is created when there is enough space in a directory.
*/
@Test
public void noNeedToEvictTest2() throws Exception {
// cache some data in a dir, then request the remaining space from the dir, the eviction plan
// should be empty.
StorageDir dir = mTestDir;
long capacity = dir.getCapacityBytes();
long cachedBytes = capacity / 2 + 1;
TieredBlockStoreTestUtils.cache(SESSION_ID, BLOCK_ID, cachedBytes, dir, mMetaManager, mEvictor);
Assert.assertTrue(mEvictor.freeSpaceWithView(capacity - cachedBytes,
dir.toBlockStoreLocation(), mManagerView).isEmpty());
}
/**
* Tests that no eviction plan is created when all directories are filled except for one
* directory.
*/
@Test
public void noNeedToEvictTest3() throws Exception {
// fill in all dirs except for one directory, then request the capacity of
// the directory with anyDirInTier
StorageDir dirLeft = mTestDir;
long blockId = BLOCK_ID; // start from BLOCK_ID
for (StorageTier tier : mMetaManager.getTiers()) {
for (StorageDir dir : tier.getStorageDirs()) {
if (dir != dirLeft) {
TieredBlockStoreTestUtils.cache(SESSION_ID, blockId, dir.getCapacityBytes(), dir,
mMetaManager, mEvictor);
blockId++;
}
}
}
Assert.assertTrue(mEvictor.freeSpaceWithView(dirLeft.getCapacityBytes(),
BlockStoreLocation.anyDirInTier(dirLeft.getParentTier().getTierAlias()), mManagerView)
.isEmpty());
}
/**
* Tests that an eviction plan is created when a directory is filled and the request size is the
* capacity of the directory.
*/
@Test
public void needToEvict() throws Exception {
// fill in a dir and request the capacity of the dir, all cached data in the dir should be
// evicted.
StorageDir dir = mTestDir;
long capacityBytes = dir.getCapacityBytes();
TieredBlockStoreTestUtils.cache(SESSION_ID, BLOCK_ID, capacityBytes, dir, mMetaManager,
mEvictor);
EvictionPlan plan =
mEvictor.freeSpaceWithView(capacityBytes, dir.toBlockStoreLocation(), mManagerView);
EvictorTestUtils.assertEvictionPlanValid(capacityBytes, plan, mMetaManager);
}
/**
* Tests that an eviction plan is created when all capacity is used in each directory in a tier
* and the request size is the capacity of the largest directory.
*/
@Test
public void needToEvictAnyDirInTier() throws Exception {
// cache data with size of "(capacity - 1)" in each dir in a tier, request size of "capacity" of
// the last dir(whose capacity is largest) in this tier from anyDirInTier(tier), all blocks
// cached in the last dir should be in the eviction plan.
StorageTier tier = mMetaManager.getTiers().get(0);
long blockId = BLOCK_ID;
List<StorageDir> dirs = tier.getStorageDirs();
for (StorageDir dir : dirs) {
TieredBlockStoreTestUtils.cache(SESSION_ID, blockId, dir.getCapacityBytes() - 1, dir,
mMetaManager, mEvictor);
blockId++;
}
long requestBytes = dirs.get(dirs.size() - 1).getCapacityBytes();
EvictionPlan plan =
mEvictor.freeSpaceWithView(requestBytes,
BlockStoreLocation.anyDirInTier(tier.getTierAlias()), mManagerView);
EvictorTestUtils.assertEvictionPlanValid(requestBytes, plan, mMetaManager);
}
/**
* Tests that an eviction plan is created when all capacity is used in each directory in all tiers
* and the request size is the minimum capacity of all directories.
*/
@Test
public void needToEvictAnyTier() throws Exception {
// cache data with size of "(capacity - 1)" in each dir in all tiers, request size of minimum
// "capacity" of all dirs from anyTier
long minCapacity = Long.MAX_VALUE;
long blockId = BLOCK_ID;
for (StorageTier tier : mMetaManager.getTiers()) {
for (StorageDir dir : tier.getStorageDirs()) {
long capacity = dir.getCapacityBytes();
minCapacity = Math.min(minCapacity, capacity);
TieredBlockStoreTestUtils
.cache(SESSION_ID, blockId, capacity - 1, dir, mMetaManager, mEvictor);
blockId++;
}
}
EvictionPlan plan =
mEvictor.freeSpaceWithView(minCapacity, BlockStoreLocation.anyTier(), mManagerView);
EvictorTestUtils.assertEvictionPlanValid(minCapacity, plan, mMetaManager);
}
/**
* Tests that no eviction plan is available when requesting more space than capacity available.
*/
@Test
public void requestSpaceLargerThanCapacity() throws Exception {
// cache data in a dir
long totalCapacity = mMetaManager.getAvailableBytes(BlockStoreLocation.anyTier());
StorageDir dir = mTestDir;
BlockStoreLocation dirLocation = dir.toBlockStoreLocation();
long dirCapacity = mMetaManager.getAvailableBytes(dirLocation);
TieredBlockStoreTestUtils.cache(SESSION_ID, BLOCK_ID, dirCapacity, dir, mMetaManager, mEvictor);
// request space larger than total capacity, no eviction plan should be available
Assert.assertNull(mEvictor.freeSpaceWithView(totalCapacity + 1, BlockStoreLocation.anyTier(),
mManagerView));
// request space larger than capacity for the random directory, no eviction plan should be
// available
Assert.assertNull(mEvictor.freeSpaceWithView(dirCapacity + 1, dirLocation, mManagerView));
}
}