// Tests a report is correctly generated after moving blocks @Test public void generateReportMoveTest() { Long block1 = 1L; Long block2 = 2L; Long block3 = 3L; moveBlock(block1, MEM_LOC); moveBlock(block2, SSD_LOC); moveBlock(block3, HDD_LOC); BlockHeartbeatReport report = mReporter.generateReport(); Map<Long, List<Long>> addedBlocks = report.getAddedBlocks(); // Block1 moved to memory List<Long> addedBlocksMem = addedBlocks.get(MEM_LOC.getStorageDirId()); Assert.assertEquals(1, addedBlocksMem.size()); Assert.assertEquals(block1, addedBlocksMem.get(0)); // Block2 moved to ssd List<Long> addedBlocksSsd = addedBlocks.get(SSD_LOC.getStorageDirId()); Assert.assertEquals(1, addedBlocksSsd.size()); Assert.assertEquals(block2, addedBlocksSsd.get(0)); // Block3 moved to hdd List<Long> addedBlocksHdd = addedBlocks.get(HDD_LOC.getStorageDirId()); Assert.assertEquals(1, addedBlocksHdd.size()); Assert.assertEquals(block3, addedBlocksHdd.get(0)); }
@Test public void reserveTest() throws Exception { // Reserve on top tier long blockId = 100; BlockStoreLocation tier0 = BlockStoreLocation.anyDirInTier(StorageLevelAlias.MEM.getValue()); for (int i = 0; i < 3; i++) { TieredBlockStoreTestUtils.cache(SESSION_ID, blockId++, BLOCK_SIZE, mBlockStore, tier0); } CommonUtils.sleepMs( WorkerContext.getConf().getLong(Constants.WORKER_SPACE_RESERVER_INTERVAL_MS)); BlockStoreMeta storeMeta = mBlockStore.getBlockStoreMeta(); Assert.assertEquals(3 * BLOCK_SIZE, storeMeta.getUsedBytes()); List<Long> usedBytesOnTiers = storeMeta.getUsedBytesOnTiers(); Assert.assertEquals( 2 * BLOCK_SIZE, (long) usedBytesOnTiers.get(StorageLevelAlias.MEM.getValue() - 1)); Assert.assertEquals( BLOCK_SIZE, (long) usedBytesOnTiers.get(StorageLevelAlias.HDD.getValue() - 1)); // Reserve on under tier for (int i = 0; i < 7; i++) { TieredBlockStoreTestUtils.cache(SESSION_ID, blockId++, BLOCK_SIZE, mBlockStore, tier0); } CommonUtils.sleepMs( WorkerContext.getConf().getLong(Constants.WORKER_SPACE_RESERVER_INTERVAL_MS)); storeMeta = mBlockStore.getBlockStoreMeta(); Assert.assertEquals(9 * BLOCK_SIZE, storeMeta.getUsedBytes()); usedBytesOnTiers = storeMeta.getUsedBytesOnTiers(); Assert.assertEquals( 2 * BLOCK_SIZE, (long) usedBytesOnTiers.get(StorageLevelAlias.MEM.getValue() - 1)); Assert.assertEquals( 7 * BLOCK_SIZE, (long) usedBytesOnTiers.get(StorageLevelAlias.HDD.getValue() - 1)); }
/** * Gets the StorageDir given its location in the store. * * @param location Location of the dir * @return the StorageDir object * @throws IllegalArgumentException if location is not a specific dir or the location is invalid */ public synchronized StorageDir getDir(BlockStoreLocation location) { if (location.equals(BlockStoreLocation.anyTier()) || location.equals(BlockStoreLocation.anyDirInTier(location.tierAlias()))) { throw new IllegalArgumentException( "Failed to get block path: " + location + " is not a specific dir."); } return getTier(location.tierAlias()).getDir(location.dir()); }
/** * Commits a block to Tachyon managed space. The block must be temporary. The block is persisted * after {@link BlockStore#commitBlock(long, long)}. The block will not be accessible until {@link * WorkerBlockMasterClient#commitBlock} succeeds. * * @param sessionId The id of the client * @param blockId The id of the block to commit * @throws BlockAlreadyExistsException if blockId already exists in committed blocks * @throws BlockDoesNotExistException if the temporary block cannot be found * @throws InvalidWorkerStateException if blockId does not belong to sessionId * @throws IOException if the block cannot be moved from temporary path to committed path * @throws WorkerOutOfSpaceException if there is no more space left to hold the block */ public void commitBlock(long sessionId, long blockId) throws BlockAlreadyExistsException, BlockDoesNotExistException, InvalidWorkerStateException, IOException, WorkerOutOfSpaceException { mBlockStore.commitBlock(sessionId, blockId); // TODO(calvin): Reconsider how to do this without heavy locking. // Block successfully committed, update master with new block metadata Long lockId = mBlockStore.lockBlock(sessionId, blockId); try { BlockMeta meta = mBlockStore.getBlockMeta(sessionId, blockId, lockId); BlockStoreLocation loc = meta.getBlockLocation(); Long length = meta.getBlockSize(); BlockStoreMeta storeMeta = mBlockStore.getBlockStoreMeta(); Long bytesUsedOnTier = storeMeta.getUsedBytesOnTiers().get(loc.tierAlias()); mBlockMasterClient.commitBlock( WorkerIdRegistry.getWorkerId(), bytesUsedOnTier, loc.tierAlias(), blockId, length); } catch (IOException ioe) { throw new IOException("Failed to commit block to master.", ioe); } finally { mBlockStore.unlockBlock(lockId); } }
// Tests a report is correctly generated after moving and then removing a block @Test public void generateReportMoveThenRemoveTest() { Long block1 = 1L; moveBlock(block1, MEM_LOC); removeBlock(block1); // The block should not be in the added blocks list BlockHeartbeatReport report = mReporter.generateReport(); Assert.assertEquals(null, report.getAddedBlocks().get(MEM_LOC.getStorageDirId())); // The block should be in the removed blocks list List<Long> removedBlocks = report.getRemovedBlocks(); Assert.assertEquals(1, removedBlocks.size()); Assert.assertTrue(removedBlocks.contains(block1)); }
/** * Moves the metadata of an existing block to another location or throws IOExceptions. * * @param blockMeta the meta data of the block to move * @param newLocation new location of the block * @return the new block metadata if success, absent otherwise * @throws IllegalArgumentException when the newLocation is not in the tiered storage * @throws NotFoundException when the block to move is not found * @throws AlreadyExistsException when the block to move already exists in the destination * @throws OutOfSpaceException when destination have no extra space to hold the block to move */ public synchronized BlockMeta moveBlockMeta(BlockMeta blockMeta, BlockStoreLocation newLocation) throws NotFoundException, AlreadyExistsException, OutOfSpaceException { // If existing location belongs to the target location, simply return the current block meta. BlockStoreLocation oldLocation = blockMeta.getBlockLocation(); if (oldLocation.belongTo(newLocation)) { LOG.info("moveBlockMeta: moving {} to {} is a noop", oldLocation, newLocation); return blockMeta; } long blockSize = blockMeta.getBlockSize(); int newTierAlias = newLocation.tierAlias(); StorageTier newTier = getTier(newTierAlias); StorageDir newDir = null; if (newLocation.equals(BlockStoreLocation.anyDirInTier(newTierAlias))) { for (StorageDir dir : newTier.getStorageDirs()) { if (dir.getAvailableBytes() >= blockSize) { newDir = dir; break; } } } else { StorageDir dir = newTier.getDir(newLocation.dir()); if (dir.getAvailableBytes() >= blockSize) { newDir = dir; } } if (newDir == null) { throw new OutOfSpaceException( "Failed to move BlockMeta: newLocation " + newLocation + " does not have enough space for " + blockSize + " bytes"); } StorageDir oldDir = blockMeta.getParentDir(); oldDir.removeBlockMeta(blockMeta); BlockMeta newBlockMeta = new BlockMeta(blockMeta.getBlockId(), blockSize, newDir); newDir.addBlockMeta(newBlockMeta); return newBlockMeta; }
/** * Gets the amount of available space of given location in bytes. Master queries the total number * of bytes available on each tier of the worker, and Evictor/Allocator often cares about the * bytes at a {@link StorageDir}. * * @param location location the check available bytes * @return available bytes * @throws IllegalArgumentException when location does not belong to tiered storage */ public synchronized long getAvailableBytes(BlockStoreLocation location) { long spaceAvailable = 0; if (location.equals(BlockStoreLocation.anyTier())) { for (StorageTier tier : mTiers) { spaceAvailable += tier.getAvailableBytes(); } return spaceAvailable; } int tierAlias = location.tierAlias(); StorageTier tier = getTier(tierAlias); // TODO: This should probably be max of the capacity bytes in the dirs? if (location.equals(BlockStoreLocation.anyDirInTier(tierAlias))) { return tier.getAvailableBytes(); } int dirIndex = location.dir(); StorageDir dir = tier.getDir(dirIndex); return dir.getAvailableBytes(); }
/** * Moves a block from its current location to a target location, currently only tier level moves * are supported * * @param sessionId The id of the client * @param blockId The id of the block to move * @param tierAlias The alias of the tier to move the block to * @throws IllegalArgumentException if tierAlias is out of range of tiered storage * @throws BlockDoesNotExistException if blockId cannot be found * @throws BlockAlreadyExistsException if blockId already exists in committed blocks of the * newLocation * @throws InvalidWorkerStateException if blockId has not been committed * @throws WorkerOutOfSpaceException if newLocation does not have enough extra space to hold the * block * @throws IOException if block cannot be moved from current location to newLocation */ public void moveBlock(long sessionId, long blockId, String tierAlias) throws BlockDoesNotExistException, BlockAlreadyExistsException, InvalidWorkerStateException, WorkerOutOfSpaceException, IOException { BlockStoreLocation dst = BlockStoreLocation.anyDirInTier(tierAlias); mBlockStore.moveBlock(sessionId, blockId, dst); }
/** * Frees space to make a specific amount of bytes available in the tier. * * @param sessionId the session ID * @param availableBytes the amount of free space in bytes * @param tierAlias the alias of the tier to free space * @throws WorkerOutOfSpaceException if there is not enough space * @throws BlockDoesNotExistException if blocks can not be found * @throws IOException if blocks fail to be moved or deleted on file system * @throws BlockAlreadyExistsException if blocks to move already exists in destination location * @throws InvalidWorkerStateException if blocks to move/evict is uncommitted */ public void freeSpace(long sessionId, long availableBytes, String tierAlias) throws WorkerOutOfSpaceException, BlockDoesNotExistException, IOException, BlockAlreadyExistsException, InvalidWorkerStateException { BlockStoreLocation location = BlockStoreLocation.anyDirInTier(tierAlias); mBlockStore.freeSpace(sessionId, availableBytes, location); }
/** * Creates a block. This method is only called from a data server. * * <p>Call {@link #getTempBlockWriterRemote(long, long)} to get a writer for writing to the block. * * @param sessionId The id of the client * @param blockId The id of the block to be created * @param tierAlias The alias of the tier to place the new block in * @param initialBytes The initial amount of bytes to be allocated * @throws IllegalArgumentException if location does not belong to tiered storage * @throws BlockAlreadyExistsException if blockId already exists, either temporary or committed, * or block in eviction plan already exists * @throws WorkerOutOfSpaceException if this Store has no more space than the initialBlockSize * @throws IOException if blocks in eviction plan fail to be moved or deleted */ public void createBlockRemote(long sessionId, long blockId, String tierAlias, long initialBytes) throws BlockAlreadyExistsException, WorkerOutOfSpaceException, IOException { BlockStoreLocation loc = BlockStoreLocation.anyDirInTier(tierAlias); TempBlockMeta createdBlock = mBlockStore.createBlockMeta(sessionId, blockId, loc, initialBytes); FileUtils.createBlockPath(createdBlock.getPath()); }
/** * Moves a block to new location only if allocator finds available space in newLocation. This * method will not trigger any eviction. Returns {@link MoveBlockResult}. * * @param sessionId session Id * @param blockId block Id * @param oldLocation the source location of the block * @param newLocation new location to move this block * @return the resulting information about the move operation * @throws BlockDoesNotExistException if block is not found * @throws BlockAlreadyExistsException if a block with same Id already exists in new location * @throws InvalidWorkerStateException if the block to move is a temp block * @throws IOException if I/O errors occur when moving block file */ private MoveBlockResult moveBlockInternal( long sessionId, long blockId, BlockStoreLocation oldLocation, BlockStoreLocation newLocation) throws BlockDoesNotExistException, BlockAlreadyExistsException, InvalidWorkerStateException, IOException { long lockId = mLockManager.lockBlock(sessionId, blockId, BlockLockType.WRITE); try { long blockSize; String srcFilePath; String dstFilePath; BlockMeta srcBlockMeta; BlockStoreLocation srcLocation; BlockStoreLocation dstLocation; mMetadataReadLock.lock(); try { if (mMetaManager.hasTempBlockMeta(blockId)) { throw new InvalidWorkerStateException(ExceptionMessage.MOVE_UNCOMMITTED_BLOCK, blockId); } srcBlockMeta = mMetaManager.getBlockMeta(blockId); srcLocation = srcBlockMeta.getBlockLocation(); srcFilePath = srcBlockMeta.getPath(); blockSize = srcBlockMeta.getBlockSize(); } finally { mMetadataReadLock.unlock(); } if (!srcLocation.belongsTo(oldLocation)) { throw new BlockDoesNotExistException( ExceptionMessage.BLOCK_NOT_FOUND_AT_LOCATION, blockId, oldLocation); } TempBlockMeta dstTempBlock = createBlockMetaInternal(sessionId, blockId, newLocation, blockSize, false); if (dstTempBlock == null) { return new MoveBlockResult(false, blockSize, null, null); } // When `newLocation` is some specific location, the `newLocation` and the `dstLocation` are // just the same; while for `newLocation` with a wildcard significance, the `dstLocation` // is a specific one with specific tier and dir which belongs to newLocation. dstLocation = dstTempBlock.getBlockLocation(); // When the dstLocation belongs to srcLocation, simply abort the tempBlockMeta just created // internally from the newLocation and return success with specific block location. if (dstLocation.belongsTo(srcLocation)) { mMetaManager.abortTempBlockMeta(dstTempBlock); return new MoveBlockResult(true, blockSize, srcLocation, dstLocation); } dstFilePath = dstTempBlock.getCommitPath(); // Heavy IO is guarded by block lock but not metadata lock. This may throw IOException. FileUtils.move(srcFilePath, dstFilePath); mMetadataWriteLock.lock(); try { // If this metadata update fails, we panic for now. // TODO(bin): Implement rollback scheme to recover from IO failures. mMetaManager.moveBlockMeta(srcBlockMeta, dstTempBlock); } catch (BlockAlreadyExistsException e) { throw Throwables.propagate(e); // we shall never reach here } catch (BlockDoesNotExistException e) { throw Throwables.propagate(e); // we shall never reach here } catch (WorkerOutOfSpaceException e) { // Only possible if session id gets cleaned between createBlockMetaInternal and // moveBlockMeta. throw Throwables.propagate(e); } finally { mMetadataWriteLock.unlock(); } return new MoveBlockResult(true, blockSize, srcLocation, dstLocation); } finally { mLockManager.unlockBlock(lockId); } }
@Override public void removeBlock(long sessionId, long blockId) throws InvalidWorkerStateException, BlockDoesNotExistException, IOException { removeBlock(sessionId, blockId, BlockStoreLocation.anyTier()); }
@Override public void moveBlock(long sessionId, long blockId, BlockStoreLocation newLocation) throws BlockDoesNotExistException, BlockAlreadyExistsException, InvalidWorkerStateException, WorkerOutOfSpaceException, IOException { moveBlock(sessionId, blockId, BlockStoreLocation.anyTier(), newLocation); }