/* * 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.file; import static org.junit.Assert.assertEquals; import alluxio.AlluxioURI; import alluxio.Configuration; import alluxio.ConfigurationTestUtils; import alluxio.PropertyKey; import alluxio.Sessions; import alluxio.client.file.FileSystem; import alluxio.client.file.URIStatus; import alluxio.exception.BlockDoesNotExistException; import alluxio.exception.InvalidWorkerStateException; import alluxio.underfs.UfsManager; import alluxio.underfs.UnderFileSystem; import alluxio.underfs.options.CreateOptions; import alluxio.util.io.BufferUtils; import alluxio.util.io.PathUtils; import alluxio.wire.FileInfo; import alluxio.worker.block.BlockWorker; import alluxio.worker.block.io.BlockReader; import alluxio.worker.block.meta.BlockMeta; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.util.concurrent.MockRateLimiter; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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.IOException; import java.io.OutputStream; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Tests {@link FileDataManager}. */ @RunWith(PowerMockRunner.class) @PrepareForTest({BlockWorker.class, BufferUtils.class, BlockMeta.class, FileSystem.class}) public final class FileDataManagerTest { private UnderFileSystem mUfs; private UfsManager mUfsManager; private BlockWorker mBlockWorker; private MockRateLimiter mMockRateLimiter; private FileDataManager mManager; private FileSystem mMockFileSystem; @Before public void before() throws Exception { mUfs = Mockito.mock(UnderFileSystem.class); mUfsManager = Mockito.mock(UfsManager.class); mBlockWorker = Mockito.mock(BlockWorker.class); mMockRateLimiter = new MockRateLimiter(Configuration.getBytes(PropertyKey.WORKER_FILE_PERSIST_RATE_LIMIT)); mManager = new FileDataManager(mBlockWorker, mMockRateLimiter.getGuavaRateLimiter(), mUfsManager); mMockFileSystem = PowerMockito.mock(FileSystem.class); PowerMockito.mockStatic(FileSystem.Factory.class); Mockito.when(FileSystem.Factory.get()).thenReturn(mMockFileSystem); Mockito.when(mUfs.isDirectory(Mockito.anyString())).thenReturn(true); Mockito.when(mUfsManager.get(Mockito.anyLong())).thenReturn(mUfs); } @After public void after() throws IOException { ConfigurationTestUtils.resetConfiguration(); } /** * Tests that a file gets persisted. */ @Test public void persistFile() throws Exception { long fileId = 1; List<Long> blockIds = Lists.newArrayList(1L, 2L); writeFileWithBlocks(fileId, blockIds); // verify file persisted assertEquals(Arrays.asList(fileId), mManager.getPersistedFiles()); // verify fastCopy called twice, once per block PowerMockito.verifyStatic(Mockito.times(2)); BufferUtils.fastCopy(Mockito.any(ReadableByteChannel.class), Mockito.any(WritableByteChannel.class)); // verify the file is not needed for another persistence Assert.assertFalse(mManager.needPersistence(fileId)); } /** * Tests that persisted file are cleared in the manager. */ @Test public void clearPersistedFiles() throws Exception { writeFileWithBlocks(1L, ImmutableList.of(2L, 3L)); mManager.clearPersistedFiles(ImmutableList.of(1L)); assertEquals(Collections.emptyList(), mManager.getPersistedFiles()); } /** * Tests the rate limiting functionality for asynchronous persistence. */ @Test public void persistFileRateLimiting() throws Exception { Configuration.set(PropertyKey.WORKER_FILE_PERSIST_RATE_LIMIT_ENABLED, "true"); Configuration.set(PropertyKey.WORKER_FILE_PERSIST_RATE_LIMIT, "100"); mMockRateLimiter = new MockRateLimiter(Configuration.getBytes(PropertyKey.WORKER_FILE_PERSIST_RATE_LIMIT)); mManager = new FileDataManager(mBlockWorker, mMockRateLimiter.getGuavaRateLimiter(), mUfsManager); long fileId = 1; List<Long> blockIds = Lists.newArrayList(1L, 2L, 3L); FileInfo fileInfo = new FileInfo(); fileInfo.setPath("test"); Mockito.when(mBlockWorker.getFileInfo(fileId)).thenReturn(fileInfo); BlockReader reader = Mockito.mock(BlockReader.class); for (long blockId : blockIds) { Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, blockId)) .thenReturn(blockId); Mockito.when(mBlockWorker.readBlockRemote(Sessions.CHECKPOINT_SESSION_ID, blockId, blockId)) .thenReturn(reader); BlockMeta mockedBlockMeta = PowerMockito.mock(BlockMeta.class); Mockito.when(mockedBlockMeta.getBlockSize()).thenReturn(100L); Mockito.when(mBlockWorker.getBlockMeta(Sessions.CHECKPOINT_SESSION_ID, blockId, blockId)) .thenReturn(mockedBlockMeta); } String ufsRoot = Configuration.get(PropertyKey.MASTER_MOUNT_TABLE_ROOT_UFS); Mockito.when(mUfs.isDirectory(ufsRoot)).thenReturn(true); OutputStream outputStream = Mockito.mock(OutputStream.class); // mock BufferUtils PowerMockito.mockStatic(BufferUtils.class); String dstPath = PathUtils.concatPath(ufsRoot, fileInfo.getPath()); fileInfo.setUfsPath(dstPath); Mockito.when(mUfs.create(dstPath)).thenReturn(outputStream); Mockito.when(mUfs.create(Mockito.anyString(), Mockito.any(CreateOptions.class))) .thenReturn(outputStream); Mockito.when(mMockFileSystem.getStatus(Mockito.any(AlluxioURI.class))).thenReturn( new URIStatus(fileInfo)); mManager.lockBlocks(fileId, blockIds); mManager.persistFile(fileId, blockIds); List<String> expectedEvents = Lists.newArrayList("R0.00", "R1.00", "R1.00"); assertEquals(expectedEvents, mMockRateLimiter.readEventsAndClear()); // Simulate waiting for 1 second. mMockRateLimiter.sleepMillis(1000); mManager.lockBlocks(fileId, blockIds); mManager.persistFile(fileId, blockIds); // The first write will go through immediately without throttling. expectedEvents = Lists.newArrayList("U1.00", "R0.00", "R1.00", "R1.00"); assertEquals(expectedEvents, mMockRateLimiter.readEventsAndClear()); // Repeat persistence without sleeping. mManager.lockBlocks(fileId, blockIds); mManager.persistFile(fileId, blockIds); expectedEvents = Lists.newArrayList("R1.00", "R1.00", "R1.00"); assertEquals(expectedEvents, mMockRateLimiter.readEventsAndClear()); } /** * Tests the blocks are unlocked correctly when exception is encountered in * {@link FileDataManager#lockBlocks(long, List)}. */ @Test public void lockBlocksErrorHandling() throws Exception { long fileId = 1; List<Long> blockIds = Lists.newArrayList(1L, 2L, 3L); Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, 1L)).thenReturn(1L); Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, 2L)).thenReturn(2L); Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, 3L)) .thenThrow(new BlockDoesNotExistException("block 3 does not exist")); try { mManager.lockBlocks(fileId, blockIds); Assert.fail("the lock should fail"); } catch (IOException e) { assertEquals( "failed to lock all blocks of file 1\n" + "alluxio.exception.BlockDoesNotExistException: block 3 does not exist\n", e.getMessage()); // verify the locks are all unlocked Mockito.verify(mBlockWorker).unlockBlock(1L); Mockito.verify(mBlockWorker).unlockBlock(2L); } } /** * Tests that the correct error message is provided when persisting a file fails. */ @Test public void errorHandling() throws Exception { long fileId = 1; List<Long> blockIds = Lists.newArrayList(1L, 2L); FileInfo fileInfo = new FileInfo(); fileInfo.setPath("test"); Mockito.when(mBlockWorker.getFileInfo(fileId)).thenReturn(fileInfo); for (long blockId : blockIds) { Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, blockId)) .thenReturn(blockId); Mockito.doThrow(new InvalidWorkerStateException("invalid worker")).when(mBlockWorker) .readBlockRemote(Sessions.CHECKPOINT_SESSION_ID, blockId, blockId); } String ufsRoot = Configuration.get(PropertyKey.MASTER_MOUNT_TABLE_ROOT_UFS); Mockito.when(mUfs.isDirectory(ufsRoot)).thenReturn(true); OutputStream outputStream = Mockito.mock(OutputStream.class); // mock BufferUtils PowerMockito.mockStatic(BufferUtils.class); String dstPath = PathUtils.concatPath(ufsRoot, fileInfo.getPath()); fileInfo.setUfsPath(dstPath); Mockito.when(mUfs.create(dstPath)).thenReturn(outputStream); Mockito.when(mUfs.create(Mockito.anyString(), Mockito.any(CreateOptions.class))) .thenReturn(outputStream); Mockito.when(mMockFileSystem.getStatus(Mockito.any(AlluxioURI.class))).thenReturn( new URIStatus(fileInfo)); mManager.lockBlocks(fileId, blockIds); try { mManager.persistFile(fileId, blockIds); Assert.fail("the persist should fail"); } catch (IOException e) { assertEquals("the blocks of file1 are failed to persist\n" + "alluxio.exception.InvalidWorkerStateException: invalid worker\n", e.getMessage()); // verify the locks are all unlocked Mockito.verify(mBlockWorker).unlockBlock(1L); Mockito.verify(mBlockWorker).unlockBlock(2L); } } private void writeFileWithBlocks(long fileId, List<Long> blockIds) throws Exception { FileInfo fileInfo = new FileInfo(); fileInfo.setPath("test"); Mockito.when(mBlockWorker.getFileInfo(fileId)).thenReturn(fileInfo); BlockReader reader = Mockito.mock(BlockReader.class); for (long blockId : blockIds) { Mockito.when(mBlockWorker.lockBlock(Sessions.CHECKPOINT_SESSION_ID, blockId)) .thenReturn(blockId); Mockito.when(mBlockWorker.readBlockRemote(Sessions.CHECKPOINT_SESSION_ID, blockId, blockId)) .thenReturn(reader); } String ufsRoot = Configuration.get(PropertyKey.MASTER_MOUNT_TABLE_ROOT_UFS); Mockito.when(mUfs.isDirectory(ufsRoot)).thenReturn(true); OutputStream outputStream = Mockito.mock(OutputStream.class); // mock BufferUtils PowerMockito.mockStatic(BufferUtils.class); String dstPath = PathUtils.concatPath(ufsRoot, fileInfo.getPath()); fileInfo.setUfsPath(dstPath); Mockito.when(mUfs.create(dstPath)).thenReturn(outputStream); Mockito.when(mUfs.create(Mockito.anyString(), Mockito.any(CreateOptions.class))) .thenReturn(outputStream); Mockito.when(mMockFileSystem.getStatus(Mockito.any(AlluxioURI.class))).thenReturn( new URIStatus(fileInfo)); mManager.lockBlocks(fileId, blockIds); mManager.persistFile(fileId, blockIds); } }