/* * 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; import alluxio.AlluxioURI; import alluxio.Constants; import alluxio.LocalAlluxioClusterResource; import alluxio.PropertyKey; import alluxio.BaseIntegrationTest; import alluxio.client.WriteType; import alluxio.client.block.BlockMasterClient; import alluxio.client.file.FileOutStream; import alluxio.client.file.FileSystem; import alluxio.client.file.FileSystemTestUtils; import alluxio.client.file.URIStatus; import alluxio.client.file.options.CreateFileOptions; import alluxio.exception.ExceptionMessage; import alluxio.exception.InvalidPathException; import alluxio.heartbeat.HeartbeatContext; import alluxio.heartbeat.HeartbeatScheduler; import alluxio.heartbeat.ManuallyScheduleHeartbeat; import alluxio.master.block.BlockId; import alluxio.thrift.AlluxioTException; import alluxio.thrift.LockBlockTOptions; import alluxio.underfs.UnderFileSystem; import alluxio.util.io.BufferUtils; import alluxio.util.io.PathUtils; import alluxio.worker.block.BlockWorker; import alluxio.worker.block.BlockWorkerClientServiceHandler; import org.apache.thrift.TException; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; /** * Integration tests for {@link BlockWorkerClientServiceHandler}. */ public class BlockServiceHandlerIntegrationTest extends BaseIntegrationTest { private static final long WORKER_CAPACITY_BYTES = 10 * Constants.MB; private static final long SESSION_ID = 1L; @ClassRule public static ManuallyScheduleHeartbeat sManuallySchedule = new ManuallyScheduleHeartbeat(HeartbeatContext.WORKER_BLOCK_SYNC); @Rule public LocalAlluxioClusterResource mLocalAlluxioClusterResource = new LocalAlluxioClusterResource.Builder() .setProperty(PropertyKey.WORKER_MEMORY_SIZE, WORKER_CAPACITY_BYTES) .setProperty(PropertyKey.USER_BLOCK_SIZE_BYTES_DEFAULT, Constants.MB) .setProperty(PropertyKey.USER_FILE_BUFFER_BYTES, String.valueOf(100)) .build(); private BlockWorkerClientServiceHandler mBlockWorkerServiceHandler = null; private FileSystem mFileSystem = null; private BlockMasterClient mBlockMasterClient; @Before public final void before() throws Exception { mFileSystem = mLocalAlluxioClusterResource.get().getClient(); mBlockWorkerServiceHandler = mLocalAlluxioClusterResource.get().getWorkerProcess().getWorker(BlockWorker.class) .getWorkerServiceHandler(); mBlockMasterClient = BlockMasterClient.Factory.create( new InetSocketAddress(mLocalAlluxioClusterResource.get().getHostname(), mLocalAlluxioClusterResource.get().getMasterRpcPort())); } @After public final void after() throws Exception { mBlockMasterClient.close(); } // Tests that caching a block successfully persists the block if the block exists @Test public void cacheBlock() throws Exception { mFileSystem.createFile(new AlluxioURI("/testFile")).close(); URIStatus file = mFileSystem.getStatus(new AlluxioURI("/testFile")); final int writeTier = Constants.FIRST_TIER; final int blockSize = (int) WORKER_CAPACITY_BYTES / 10; // Construct the block ids for the file. final long blockId0 = BlockId.createBlockId(BlockId.getContainerId(file.getFileId()), 0); final long blockId1 = BlockId.createBlockId(BlockId.getContainerId(file.getFileId()), 1); String filename = mBlockWorkerServiceHandler.requestBlockLocation(SESSION_ID, blockId0, blockSize, writeTier); createBlockFile(filename, blockSize); mBlockWorkerServiceHandler.cacheBlock(SESSION_ID, blockId0); // The master should be immediately updated with the persisted block Assert.assertEquals(blockSize, mBlockMasterClient.getUsedBytes()); // Attempting to cache a non existent block should throw an exception Exception exception = null; try { mBlockWorkerServiceHandler.cacheBlock(SESSION_ID, blockId1); } catch (TException e) { exception = e; } Assert.assertNotNull(exception); } // Tests that cancelling a block will remove the temporary file @Test public void cancelBlock() throws Exception { mFileSystem.createFile(new AlluxioURI("/testFile")).close(); URIStatus file = mFileSystem.getStatus(new AlluxioURI("/testFile")); final int writeTier = Constants.FIRST_TIER; final int blockSize = (int) WORKER_CAPACITY_BYTES / 2; final long blockId = BlockId.createBlockId(BlockId.getContainerId(file.getFileId()), 0); String filename = mBlockWorkerServiceHandler.requestBlockLocation(SESSION_ID, blockId, blockSize, writeTier); createBlockFile(filename, blockSize); mBlockWorkerServiceHandler.cancelBlock(SESSION_ID, blockId); // The block should not exist after being cancelled Assert.assertFalse(new File(filename).exists()); // The master should not have recorded any used space after the block is cancelled HeartbeatScheduler.execute(HeartbeatContext.WORKER_BLOCK_SYNC); Assert.assertEquals(0, mBlockMasterClient.getUsedBytes()); } // Tests that lock block returns the correct path @Test public void lockBlock() throws Exception { final int blockSize = (int) WORKER_CAPACITY_BYTES / 2; CreateFileOptions options = CreateFileOptions.defaults().setBlockSizeBytes(blockSize) .setWriteType(WriteType.MUST_CACHE); FileOutStream out = mFileSystem.createFile(new AlluxioURI("/testFile"), options); URIStatus file = mFileSystem.getStatus(new AlluxioURI("/testFile")); final long blockId = BlockId.createBlockId(BlockId.getContainerId(file.getFileId()), 0); out.write(BufferUtils.getIncreasingByteArray(blockSize)); out.close(); String localPath = mBlockWorkerServiceHandler.lockBlock(blockId, SESSION_ID, new LockBlockTOptions()) .getBlockPath(); // The local path should exist Assert.assertNotNull(localPath); UnderFileSystem ufs = UnderFileSystem.Factory.create(localPath); byte[] data = new byte[blockSize]; InputStream in = ufs.open(localPath); int bytesRead = in.read(data); // The data in the local file should equal the data we wrote earlier Assert.assertEquals(blockSize, bytesRead); Assert.assertTrue(BufferUtils.equalIncreasingByteArray(bytesRead, data)); mBlockWorkerServiceHandler.unlockBlock(blockId, SESSION_ID); } // Tests that lock block returns error on failure @Test public void lockBlockFailure() throws Exception { mFileSystem.createFile(new AlluxioURI("/testFile")).close(); URIStatus file = mFileSystem.getStatus(new AlluxioURI("/testFile")); final long blockId = BlockId.createBlockId(BlockId.getContainerId(file.getFileId()), 0); Exception exception = null; try { mBlockWorkerServiceHandler.lockBlock(blockId, SESSION_ID, new LockBlockTOptions()); } catch (AlluxioTException e) { exception = e; } // A file does not exist exception should have been thrown Assert.assertNotNull(exception); } // Tests that files are evicted when there is not enough space in the worker. @Test public void eviction() throws Exception { final int blockSize = (int) WORKER_CAPACITY_BYTES / 2; AlluxioURI file1 = new AlluxioURI("/file1"); FileSystemTestUtils.createByteFile(mFileSystem, file1, WriteType.MUST_CACHE, blockSize); // File should be in memory after it is written with MUST_CACHE URIStatus fileInfo1 = mFileSystem.getStatus(file1); Assert.assertEquals(100, fileInfo1.getInMemoryPercentage()); AlluxioURI file2 = new AlluxioURI("/file2"); FileSystemTestUtils.createByteFile(mFileSystem, file2, WriteType.MUST_CACHE, blockSize); // Both file 1 and 2 should be in memory since the combined size is not larger than worker space fileInfo1 = mFileSystem.getStatus(file1); URIStatus fileInfo2 = mFileSystem.getStatus(file2); Assert.assertEquals(100, fileInfo1.getInMemoryPercentage()); Assert.assertEquals(100, fileInfo2.getInMemoryPercentage()); AlluxioURI file3 = new AlluxioURI("/file3"); FileSystemTestUtils.createByteFile(mFileSystem, file3, WriteType.MUST_CACHE, blockSize); HeartbeatScheduler.execute(HeartbeatContext.WORKER_BLOCK_SYNC); fileInfo1 = mFileSystem.getStatus(file1); fileInfo2 = mFileSystem.getStatus(file2); URIStatus fileInfo3 = mFileSystem.getStatus(file3); // File 3 should be in memory and one of file 1 or 2 should be in memory Assert.assertEquals(100, fileInfo3.getInMemoryPercentage()); Assert.assertTrue("Exactly one of file1 and file2 should be 100% in memory", fileInfo1.getInMemoryPercentage() == 100 ^ fileInfo2.getInMemoryPercentage() == 100); } // Tests that space will be allocated when possible @Test public void requestSpace() throws Exception { final long blockId1 = 12345L; final long blockId2 = 12346L; final int chunkSize = (int) WORKER_CAPACITY_BYTES / 10; /* only a single tier, so SecondHighest still refers to the memory tier */ final int writeTier = Constants.SECOND_TIER; mBlockWorkerServiceHandler.requestBlockLocation(SESSION_ID, blockId1, chunkSize, writeTier); boolean result = mBlockWorkerServiceHandler.requestSpace(SESSION_ID, blockId1, chunkSize); // Initial request and first additional request should succeed Assert.assertTrue(result); result = mBlockWorkerServiceHandler.requestSpace(SESSION_ID, blockId1, WORKER_CAPACITY_BYTES); // Impossible request should fail Assert.assertFalse(result); // Request for space on a nonexistent block should fail try { mBlockWorkerServiceHandler.requestSpace(SESSION_ID, blockId2, chunkSize); Assert.fail(); } catch (AlluxioTException e) { Assert.assertEquals(ExceptionMessage.TEMP_BLOCK_META_NOT_FOUND.getMessage(blockId2), e.getMessage()); } // Request for impossible initial space should fail Exception exception = null; try { mBlockWorkerServiceHandler.requestBlockLocation(SESSION_ID, blockId2, WORKER_CAPACITY_BYTES + 1, writeTier); } catch (AlluxioTException e) { exception = e; } Assert.assertNotNull(exception); } // Tests that multiple users cannot request a combined space greater than worker space @Test public void totalOverCapacityRequestSpace() throws Exception { final int chunkSize = (int) WORKER_CAPACITY_BYTES / 2; final long userId1 = SESSION_ID; final long userId2 = SESSION_ID + 1; final long blockId1 = 12345L; final long blockId2 = 23456L; /* only a single tier, so lowest still refers to the memory tier */ final int writeTier = Constants.LAST_TIER; String filePath1 = mBlockWorkerServiceHandler.requestBlockLocation(userId1, blockId1, chunkSize, writeTier); String filePath2 = mBlockWorkerServiceHandler.requestBlockLocation(userId2, blockId2, chunkSize, writeTier); // Initial requests should succeed Assert.assertTrue(filePath1 != null); Assert.assertTrue(filePath2 != null); // Additional requests for space should fail Assert.assertFalse(mBlockWorkerServiceHandler.requestSpace(userId1, blockId1, chunkSize)); Assert.assertFalse(mBlockWorkerServiceHandler.requestSpace(userId2, blockId2, chunkSize)); } // Creates a block file and write an increasing byte array into it private void createBlockFile(String filename, int len) throws IOException, InvalidPathException { UnderFileSystem ufs = UnderFileSystem.Factory.create(filename); ufs.mkdirs(PathUtils.getParent(filename)); OutputStream out = ufs.create(filename); out.write(BufferUtils.getIncreasingByteArray(len), 0, len); out.close(); } }