/* * 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.meta; import alluxio.exception.BlockAlreadyExistsException; import alluxio.exception.BlockDoesNotExistException; import alluxio.exception.ExceptionMessage; import alluxio.exception.InvalidWorkerStateException; import alluxio.exception.WorkerOutOfSpaceException; import alluxio.util.io.FileUtils; import alluxio.worker.block.BlockStoreLocation; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.concurrent.NotThreadSafe; /** * Represents a directory in a storage tier. It has a fixed capacity allocated to it on * instantiation. It contains the set of blocks currently in the storage directory. */ @NotThreadSafe public final class StorageDir { private static final Logger LOG = LoggerFactory.getLogger(StorageDir.class); private final long mCapacityBytes; /** A map from block id to block metadata. */ private Map<Long, BlockMeta> mBlockIdToBlockMap; /** A map from block id to temp block metadata. */ private Map<Long, TempBlockMeta> mBlockIdToTempBlockMap; /** A map from session id to the set of temp blocks created by this session. */ private Map<Long, Set<Long>> mSessionIdToTempBlockIdsMap; private AtomicLong mAvailableBytes; private AtomicLong mCommittedBytes; private String mDirPath; private int mDirIndex; private StorageTier mTier; private StorageDir(StorageTier tier, int dirIndex, long capacityBytes, String dirPath) { mTier = Preconditions.checkNotNull(tier); mDirIndex = dirIndex; mCapacityBytes = capacityBytes; mAvailableBytes = new AtomicLong(capacityBytes); mCommittedBytes = new AtomicLong(0); mDirPath = dirPath; mBlockIdToBlockMap = new HashMap<>(200); mBlockIdToTempBlockMap = new HashMap<>(200); mSessionIdToTempBlockIdsMap = new HashMap<>(200); } /** * Factory method to create {@link StorageDir}. * * It will load metadata of existing committed blocks in the dirPath specified. Only files with * directory depth 1 under dirPath and whose file name can be parsed into {@code long} will be * considered as existing committed blocks, these files will be preserved, others files or * directories will be deleted. * * @param tier the {@link StorageTier} this dir belongs to * @param dirIndex the index of this dir in its tier * @param capacityBytes the initial capacity of this dir, can not be modified later * @param dirPath filesystem path of this dir for actual storage * @return the new created {@link StorageDir} * @throws BlockAlreadyExistsException when metadata of existing committed blocks already exists * @throws WorkerOutOfSpaceException when metadata can not be added due to limited left space */ public static StorageDir newStorageDir(StorageTier tier, int dirIndex, long capacityBytes, String dirPath) throws BlockAlreadyExistsException, IOException, WorkerOutOfSpaceException { StorageDir dir = new StorageDir(tier, dirIndex, capacityBytes, dirPath); dir.initializeMeta(); return dir; } /** * Initializes metadata for existing blocks in this {@link StorageDir}. * * Only paths satisfying the contract defined in * {@link AbstractBlockMeta#commitPath(StorageDir, long)} are legal, should be in format like * {dir}/{blockId}. other paths will be deleted. * * @throws BlockAlreadyExistsException when metadata of existing committed blocks already exists * @throws WorkerOutOfSpaceException when metadata can not be added due to limited left space */ private void initializeMeta() throws BlockAlreadyExistsException, IOException, WorkerOutOfSpaceException { // Create the storage directory path FileUtils.createStorageDirPath(mDirPath); File dir = new File(mDirPath); File[] paths = dir.listFiles(); if (paths == null) { return; } for (File path : paths) { if (!path.isFile()) { LOG.error("{} in StorageDir is not a file", path.getAbsolutePath()); try { // TODO(calvin): Resolve this conflict in class names. org.apache.commons.io.FileUtils.deleteDirectory(path); } catch (IOException e) { LOG.error("can not delete directory {}", path.getAbsolutePath(), e); } } else { try { long blockId = Long.parseLong(path.getName()); addBlockMeta(new BlockMeta(blockId, path.length(), this)); } catch (NumberFormatException e) { LOG.error("filename of {} in StorageDir can not be parsed into long", path.getAbsolutePath(), e); if (path.delete()) { LOG.warn("file {} has been deleted", path.getAbsolutePath()); } else { LOG.error("can not delete file {}", path.getAbsolutePath()); } } } } } /** * Gets the total capacity of this {@link StorageDir} in bytes, which is a constant once this * {@link StorageDir} has been initialized. * * @return the total capacity of this {@link StorageDir} in bytes */ public long getCapacityBytes() { return mCapacityBytes; } /** * Gets the total available capacity of this {@link StorageDir} in bytes. This value equals the * total capacity of this {@link StorageDir}, minus the used bytes by committed blocks and temp * blocks. * * @return available capacity in bytes */ public long getAvailableBytes() { return mAvailableBytes.get(); } /** * Gets the total size of committed blocks in this StorageDir in bytes. * * @return number of committed bytes */ public long getCommittedBytes() { return mCommittedBytes.get(); } /** * @return the path of the directory */ public String getDirPath() { return mDirPath; } /** * Returns the {@link StorageTier} containing this {@link StorageDir}. * * @return {@link StorageTier} */ public StorageTier getParentTier() { return mTier; } /** * Returns the zero-based index of this dir in its parent {@link StorageTier}. * * @return index */ public int getDirIndex() { return mDirIndex; } /** * Returns the list of block ids in this dir. * * @return a list of block ids */ public List<Long> getBlockIds() { return new ArrayList<>(mBlockIdToBlockMap.keySet()); } /** * Returns the list of blocks stored in this dir. * * @return a list of blocks */ public List<BlockMeta> getBlocks() { return new ArrayList<>(mBlockIdToBlockMap.values()); } /** * Checks if a block is in this storage dir. * * @param blockId the block id * @return true if the block is in this storage dir, false otherwise */ public boolean hasBlockMeta(long blockId) { return mBlockIdToBlockMap.containsKey(blockId); } /** * Checks if a temp block is in this storage dir. * * @param blockId the block id * @return true if the block is in this storage dir, false otherwise */ public boolean hasTempBlockMeta(long blockId) { return mBlockIdToTempBlockMap.containsKey(blockId); } /** * Gets the {@link BlockMeta} from this storage dir by its block id. * * @param blockId the block id * @return {@link BlockMeta} of the given block or null * @throws BlockDoesNotExistException if no block is found */ public BlockMeta getBlockMeta(long blockId) throws BlockDoesNotExistException { BlockMeta blockMeta = mBlockIdToBlockMap.get(blockId); if (blockMeta == null) { throw new BlockDoesNotExistException(ExceptionMessage.BLOCK_META_NOT_FOUND, blockId); } return blockMeta; } /** * Gets the {@link BlockMeta} from this storage dir by its block id. * * @param blockId the block id * @return {@link TempBlockMeta} of the given block or null */ public TempBlockMeta getTempBlockMeta(long blockId) { return mBlockIdToTempBlockMap.get(blockId); } /** * Adds the metadata of a new block into this storage dir. * * @param blockMeta the metadata of the block * @throws BlockAlreadyExistsException if blockId already exists * @throws WorkerOutOfSpaceException when not enough space to hold block */ public void addBlockMeta(BlockMeta blockMeta) throws WorkerOutOfSpaceException, BlockAlreadyExistsException { Preconditions.checkNotNull(blockMeta); long blockId = blockMeta.getBlockId(); long blockSize = blockMeta.getBlockSize(); if (getAvailableBytes() < blockSize) { throw new WorkerOutOfSpaceException(ExceptionMessage.NO_SPACE_FOR_BLOCK_META, blockId, blockSize, getAvailableBytes(), blockMeta.getBlockLocation().tierAlias()); } if (hasBlockMeta(blockId)) { throw new BlockAlreadyExistsException(ExceptionMessage.ADD_EXISTING_BLOCK, blockId, blockMeta .getBlockLocation().tierAlias()); } mBlockIdToBlockMap.put(blockId, blockMeta); reserveSpace(blockSize, true); } /** * Adds the metadata of a new block into this storage dir. * * @param tempBlockMeta the metadata of a temp block to add * @throws BlockAlreadyExistsException if blockId already exists * @throws WorkerOutOfSpaceException when not enough space to hold block */ public void addTempBlockMeta(TempBlockMeta tempBlockMeta) throws WorkerOutOfSpaceException, BlockAlreadyExistsException { Preconditions.checkNotNull(tempBlockMeta); long sessionId = tempBlockMeta.getSessionId(); long blockId = tempBlockMeta.getBlockId(); long blockSize = tempBlockMeta.getBlockSize(); if (getAvailableBytes() < blockSize) { throw new WorkerOutOfSpaceException(ExceptionMessage.NO_SPACE_FOR_BLOCK_META, blockId, blockSize, getAvailableBytes(), tempBlockMeta.getBlockLocation().tierAlias()); } if (hasTempBlockMeta(blockId)) { throw new BlockAlreadyExistsException(ExceptionMessage.ADD_EXISTING_BLOCK, blockId, tempBlockMeta.getBlockLocation().tierAlias()); } mBlockIdToTempBlockMap.put(blockId, tempBlockMeta); Set<Long> sessionTempBlocks = mSessionIdToTempBlockIdsMap.get(sessionId); if (sessionTempBlocks == null) { mSessionIdToTempBlockIdsMap.put(sessionId, Sets.newHashSet(blockId)); } else { sessionTempBlocks.add(blockId); } reserveSpace(blockSize, false); } /** * Removes a block from this storage dir. * * @param blockMeta the metadata of the block * @throws BlockDoesNotExistException if no block is found */ public void removeBlockMeta(BlockMeta blockMeta) throws BlockDoesNotExistException { Preconditions.checkNotNull(blockMeta); long blockId = blockMeta.getBlockId(); BlockMeta deletedBlockMeta = mBlockIdToBlockMap.remove(blockId); if (deletedBlockMeta == null) { throw new BlockDoesNotExistException(ExceptionMessage.BLOCK_META_NOT_FOUND, blockId); } reclaimSpace(blockMeta.getBlockSize(), true); } /** * Removes a temp block from this storage dir. * * @param tempBlockMeta the metadata of the temp block to remove * @throws BlockDoesNotExistException if no temp block is found */ public void removeTempBlockMeta(TempBlockMeta tempBlockMeta) throws BlockDoesNotExistException { Preconditions.checkNotNull(tempBlockMeta); final long blockId = tempBlockMeta.getBlockId(); final long sessionId = tempBlockMeta.getSessionId(); TempBlockMeta deletedTempBlockMeta = mBlockIdToTempBlockMap.remove(blockId); if (deletedTempBlockMeta == null) { throw new BlockDoesNotExistException(ExceptionMessage.BLOCK_META_NOT_FOUND, blockId); } Set<Long> sessionBlocks = mSessionIdToTempBlockIdsMap.get(sessionId); if (sessionBlocks == null || !sessionBlocks.contains(blockId)) { throw new BlockDoesNotExistException(ExceptionMessage.BLOCK_NOT_FOUND_FOR_SESSION, blockId, mTier.getTierAlias(), sessionId); } Preconditions.checkState(sessionBlocks.remove(blockId)); if (sessionBlocks.isEmpty()) { mSessionIdToTempBlockIdsMap.remove(sessionId); } reclaimSpace(tempBlockMeta.getBlockSize(), false); } /** * Changes the size of a temp block. * * @param tempBlockMeta the metadata of the temp block to resize * @param newSize the new size after change in bytes * @throws InvalidWorkerStateException when newSize is smaller than oldSize */ public void resizeTempBlockMeta(TempBlockMeta tempBlockMeta, long newSize) throws InvalidWorkerStateException { long oldSize = tempBlockMeta.getBlockSize(); if (newSize > oldSize) { reserveSpace(newSize - oldSize, false); tempBlockMeta.setBlockSize(newSize); } else if (newSize < oldSize) { throw new InvalidWorkerStateException("Shrinking block, not supported!"); } } /** * Cleans up the temp block metadata for each block id passed in. * * @param sessionId the id of the client associated with the temporary blocks * @param tempBlockIds the list of temporary blocks to clean up, non temporary blocks or * nonexistent blocks will be ignored */ public void cleanupSessionTempBlocks(long sessionId, List<Long> tempBlockIds) { Set<Long> sessionTempBlocks = mSessionIdToTempBlockIdsMap.get(sessionId); // The session's temporary blocks have already been removed. if (sessionTempBlocks == null) { return; } for (Long tempBlockId : tempBlockIds) { if (!mBlockIdToTempBlockMap.containsKey(tempBlockId)) { // This temp block does not exist in this dir, this is expected for some blocks since the // input list is across all dirs continue; } sessionTempBlocks.remove(tempBlockId); TempBlockMeta tempBlockMeta = mBlockIdToTempBlockMap.remove(tempBlockId); if (tempBlockMeta != null) { reclaimSpace(tempBlockMeta.getBlockSize(), false); } else { LOG.error("Cannot find blockId {} when cleanup sessionId {}", tempBlockId, sessionId); } } if (sessionTempBlocks.isEmpty()) { mSessionIdToTempBlockIdsMap.remove(sessionId); } else { // This may happen if the client comes back during clean up and creates more blocks or some // temporary blocks failed to be deleted LOG.warn("Blocks still owned by session {} after cleanup.", sessionId); } } /** * Gets the temporary blocks associated with a session in this {@link StorageDir}, an empty list * is returned if the session has no temporary blocks in this {@link StorageDir}. * * @param sessionId the id of the session * @return A list of temporary blocks the session is associated with in this {@link StorageDir} */ public List<TempBlockMeta> getSessionTempBlocks(long sessionId) { Set<Long> sessionTempBlockIds = mSessionIdToTempBlockIdsMap.get(sessionId); if (sessionTempBlockIds == null || sessionTempBlockIds.isEmpty()) { return Collections.emptyList(); } List<TempBlockMeta> sessionTempBlocks = new ArrayList<>(); for (long blockId : sessionTempBlockIds) { sessionTempBlocks.add(mBlockIdToTempBlockMap.get(blockId)); } return sessionTempBlocks; } /** * @return the block store location of this directory */ public BlockStoreLocation toBlockStoreLocation() { return new BlockStoreLocation(mTier.getTierAlias(), mDirIndex); } private void reclaimSpace(long size, boolean committed) { Preconditions.checkState(mCapacityBytes >= mAvailableBytes.get() + size, "Available bytes should always be less than total capacity bytes"); mAvailableBytes.addAndGet(size); if (committed) { mCommittedBytes.addAndGet(-size); } } private void reserveSpace(long size, boolean committed) { Preconditions.checkState(size <= mAvailableBytes.get(), "Available bytes should always be non-negative"); mAvailableBytes.addAndGet(-size); if (committed) { mCommittedBytes.addAndGet(size); } } }