/*
* 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.client.block;
import alluxio.client.WriteType;
import alluxio.client.block.options.LockBlockOptions;
import alluxio.client.file.FileSystemContext;
import alluxio.client.file.options.InStreamOptions;
import alluxio.client.file.options.OutStreamOptions;
import alluxio.client.file.policy.FileWriteLocationPolicy;
import alluxio.client.resource.LockBlockResource;
import alluxio.exception.PreconditionMessage;
import alluxio.network.protocol.RPCMessageDecoder;
import alluxio.resource.DummyCloseableResource;
import alluxio.util.network.NetworkAddressUtils;
import alluxio.wire.BlockInfo;
import alluxio.wire.BlockLocation;
import alluxio.wire.LockBlockResult;
import alluxio.wire.WorkerNetAddress;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
/**
* Tests for {@link AlluxioBlockStore}.
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({FileSystemContext.class})
public final class AlluxioBlockStoreTest {
private static final long BLOCK_ID = 3L;
private static final long BLOCK_LENGTH = 100L;
private static final long LOCK_ID = 44L;
private static final String WORKER_HOSTNAME_LOCAL = NetworkAddressUtils.getLocalHostName();
private static final String WORKER_HOSTNAME_REMOTE = "remote";
private static final WorkerNetAddress WORKER_NET_ADDRESS_LOCAL = new WorkerNetAddress()
.setHost(WORKER_HOSTNAME_LOCAL);
private static final WorkerNetAddress WORKER_NET_ADDRESS_REMOTE = new WorkerNetAddress()
.setHost(WORKER_HOSTNAME_REMOTE);
private static final BlockLocation BLOCK_LOCATION_LOCAL = new BlockLocation()
.setWorkerAddress(WORKER_NET_ADDRESS_LOCAL);
private static final BlockLocation BLOCK_LOCATION_REMOTE = new BlockLocation()
.setWorkerAddress(WORKER_NET_ADDRESS_REMOTE);
/**
* A mock class used to return controlled result when selecting workers.
*/
@ThreadSafe
private static class MockFileWriteLocationPolicy implements FileWriteLocationPolicy {
private final List<WorkerNetAddress> mWorkerNetAddresses;
private int mIndex;
/**
* Constructs this mock policy that returns the given result, once a time, in the input order.
*
* @param addresses list of addresses this mock policy will return
*/
public MockFileWriteLocationPolicy(List<WorkerNetAddress> addresses) {
mWorkerNetAddresses = Lists.newArrayList(addresses);
mIndex = 0;
}
@Override
public WorkerNetAddress getWorkerForNextBlock(Iterable<BlockWorkerInfo> workerInfoList,
long blockSizeBytes) {
return mWorkerNetAddresses.get(mIndex++);
}
}
/**
* The rule for a temporary folder.
*/
@Rule
public TemporaryFolder mTestFolder = new TemporaryFolder();
private BlockMasterClient mMasterClient;
private BlockWorkerClient mBlockWorkerClient;
private AlluxioBlockStore mBlockStore;
private Channel mChannel;
private ChannelPipeline mPipeline;
private WorkerNetAddress mLocalAddr;
private FileSystemContext mContext;
@Before
public void before() throws Exception {
mBlockWorkerClient = PowerMockito.mock(BlockWorkerClient.class);
mMasterClient = PowerMockito.mock(BlockMasterClient.class);
mChannel = PowerMockito.mock(Channel.class);
mPipeline = PowerMockito.mock(ChannelPipeline.class);
mContext = PowerMockito.mock(FileSystemContext.class);
// Mock block store context to return our mock clients
Mockito.when(mContext.createBlockWorkerClient(Mockito.any(WorkerNetAddress.class)))
.thenReturn(mBlockWorkerClient);
Mockito.when(mContext.acquireBlockMasterClientResource())
.thenReturn(new DummyCloseableResource<>(mMasterClient));
mLocalAddr = new WorkerNetAddress().setHost(NetworkAddressUtils.getLocalHostName());
Mockito.when(mBlockWorkerClient.getWorkerNetAddress()).thenReturn(mLocalAddr);
mBlockStore = new AlluxioBlockStore(mContext, WORKER_HOSTNAME_LOCAL);
Mockito.when(mContext.acquireNettyChannel(Mockito.any(WorkerNetAddress.class)))
.thenReturn(mChannel);
Mockito.when(mChannel.pipeline()).thenReturn(mPipeline);
Mockito.when(mPipeline.last()).thenReturn(new RPCMessageDecoder());
Mockito.when(mPipeline.addLast(Mockito.any(ChannelHandler.class))).thenReturn(mPipeline);
}
/**
* Tests {@link AlluxioBlockStore#getInStream(long, InStreamOptions)} when a local block
* exists, making sure that the local block is preferred.
*/
@Test
public void getInStreamLocal() throws Exception {
Mockito.when(mMasterClient.getBlockInfo(BLOCK_ID)).thenReturn(new BlockInfo()
.setLocations(Arrays.asList(BLOCK_LOCATION_REMOTE, BLOCK_LOCATION_LOCAL)));
File mTestFile = mTestFolder.newFile("testFile");
// When a block lock for id BLOCK_ID is requested, a path to a temporary file is returned
Mockito.when(mBlockWorkerClient.lockBlock(BLOCK_ID, LockBlockOptions.defaults())).thenReturn(
new LockBlockResource(mBlockWorkerClient,
new LockBlockResult().setLockId(LOCK_ID).setBlockPath(mTestFile.getAbsolutePath()),
BLOCK_ID));
InputStream stream = mBlockStore.getInStream(BLOCK_ID, InStreamOptions.defaults());
Assert.assertEquals(alluxio.client.block.stream.BlockInStream.class, stream.getClass());
}
/**
* Tests {@link AlluxioBlockStore#getInStream(long, InStreamOptions)} when no local block
* exists, making sure that the first {@link BlockLocation} in the {@link BlockInfo} list is
* chosen.
*/
@Test
public void getInStreamRemote() throws Exception {
Mockito.when(mMasterClient.getBlockInfo(BLOCK_ID)).thenReturn(new BlockInfo()
.setLocations(Arrays.asList(BLOCK_LOCATION_REMOTE)));
File mTestFile = mTestFolder.newFile("testFile");
// When a block lock for id BLOCK_ID is requested, a path to a temporary file is returned
Mockito.when(mBlockWorkerClient.lockBlock(BLOCK_ID, LockBlockOptions.defaults())).thenReturn(
new LockBlockResource(mBlockWorkerClient,
new LockBlockResult().setLockId(LOCK_ID).setBlockPath(mTestFile.getAbsolutePath()),
BLOCK_ID));
InputStream stream = mBlockStore.getInStream(BLOCK_ID, InStreamOptions.defaults());
Assert.assertEquals(alluxio.client.block.stream.BlockInStream.class, stream.getClass());
}
@Test
public void getOutStreamUsingLocationPolicy() throws Exception {
OutStreamOptions options = OutStreamOptions.defaults().setWriteType(WriteType.MUST_CACHE)
.setLocationPolicy(new FileWriteLocationPolicy() {
@Override
public WorkerNetAddress getWorkerForNextBlock(Iterable<BlockWorkerInfo> workerInfoList,
long blockSizeBytes) {
throw new RuntimeException("policy threw exception");
}
});
try {
mBlockStore.getOutStream(BLOCK_ID, BLOCK_LENGTH, options);
Assert.fail("An exception should have been thrown");
} catch (Exception e) {
Assert.assertEquals("policy threw exception", e.getMessage());
}
}
@Test
public void getOutStreamMissingLocationPolicy() throws IOException {
OutStreamOptions options =
OutStreamOptions.defaults().setBlockSizeBytes(BLOCK_LENGTH)
.setWriteType(WriteType.MUST_CACHE).setLocationPolicy(null);
try {
mBlockStore.getOutStream(BLOCK_ID, BLOCK_LENGTH, options);
Assert.fail("missing location policy should fail");
} catch (NullPointerException e) {
Assert.assertEquals(PreconditionMessage.FILE_WRITE_LOCATION_POLICY_UNSPECIFIED.toString(),
e.getMessage());
}
}
@Test
public void getOutStreamLocal() throws Exception {
File tmp = mTestFolder.newFile();
Mockito.when(mBlockWorkerClient
.requestBlockLocation(Matchers.eq(BLOCK_ID), Matchers.anyLong(), Matchers.anyInt()))
.thenReturn(tmp.getAbsolutePath());
OutStreamOptions options = OutStreamOptions.defaults().setBlockSizeBytes(BLOCK_LENGTH)
.setLocationPolicy(new MockFileWriteLocationPolicy(
Lists.newArrayList(WORKER_NET_ADDRESS_LOCAL)))
.setWriteType(WriteType.MUST_CACHE);
OutputStream stream = mBlockStore.getOutStream(BLOCK_ID, BLOCK_LENGTH, options);
Assert.assertEquals(alluxio.client.block.stream.BlockOutStream.class, stream.getClass());
}
@Test
public void getOutStreamRemote() throws Exception {
OutStreamOptions options = OutStreamOptions.defaults().setBlockSizeBytes(BLOCK_LENGTH)
.setLocationPolicy(new MockFileWriteLocationPolicy(
Lists.newArrayList(WORKER_NET_ADDRESS_REMOTE)))
.setWriteType(WriteType.MUST_CACHE);
OutputStream stream = mBlockStore.getOutStream(BLOCK_ID, BLOCK_LENGTH, options);
Assert.assertEquals(alluxio.client.block.stream.BlockOutStream.class, stream.getClass());
}
}