tachyon.worker.block.meta.StorageDir Maven / Gradle / Ivy
/*
* Licensed to the University of California, Berkeley under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package tachyon.worker.block.meta;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import tachyon.Constants;
import tachyon.StorageDirId;
import tachyon.StorageLevelAlias;
import tachyon.exception.BlockAlreadyExistsException;
import tachyon.exception.BlockDoesNotExistException;
import tachyon.exception.ExceptionMessage;
import tachyon.exception.InvalidWorkerStateException;
import tachyon.exception.WorkerOutOfSpaceException;
import tachyon.util.io.FileUtils;
import tachyon.worker.block.BlockStoreLocation;
/**
* 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.
*
* This class does not guarantee thread safety.
*/
public final class StorageDir {
private static final Logger LOG = LoggerFactory.getLogger(Constants.LOGGER_TYPE);
private final long mCapacityBytes;
/** A map from block ID to block meta data */
private Map mBlockIdToBlockMap;
/** A map from block ID to temp block meta data */
private Map mBlockIdToTempBlockMap;
/** A map from session ID to the set of temp blocks created by this session */
private Map> 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 meta data 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 StorageDir
* @throws BlockAlreadyExistsException when meta data of existing committed blocks already exists
* @throws IOException if the storage directory cannot be created with the appropriate permissions
* @throws WorkerOutOfSpaceException when meta data 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 meta data for existing blocks in this StorageDir.
*
* Only paths satisfying the contract defined in {@link BlockMetaBase#commitPath} are legal,
* should be in format like {dir}/{blockId}. other paths will be deleted.
*
* @throws BlockAlreadyExistsException when meta data of existing committed blocks already exists
* @throws IOException if the storage directory cannot be created with the appropriate permissions
* @throws WorkerOutOfSpaceException when meta data 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 ioe) {
LOG.error("can not delete directory {}: {}", path.getAbsolutePath(), ioe);
}
} else {
try {
long blockId = Long.valueOf(path.getName());
addBlockMeta(new BlockMeta(blockId, path.length(), this));
} catch (NumberFormatException nfe) {
LOG.error("filename of {} in StorageDir can not be parsed into long",
path.getAbsolutePath());
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 StorageDir in bytes, which is a constant once this StorageDir
* has been initialized.
*
* @return the total capacity of this StorageDir in bytes
*/
public long getCapacityBytes() {
return mCapacityBytes;
}
/**
* Gets the total available capacity of this StorageDir in bytes. This value equals the total
* capacity of this 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();
}
public String getDirPath() {
return mDirPath;
}
/**
* Returns the StorageTier containing this StorageDir.
*
* @return StorageTier
*/
public StorageTier getParentTier() {
return mTier;
}
/**
* Returns the zero-based index of this dir in its parent StorageTier.
*
* @return index
*/
public int getDirIndex() {
return mDirIndex;
}
// TODO(bin): Deprecate this method.
public long getStorageDirId() {
int level = mTier.getTierLevel();
int storageLevelAliasValue = mTier.getTierAlias();
return StorageDirId.getStorageDirId(level, storageLevelAliasValue, mDirIndex);
}
/**
* Returns the list of block IDs in this dir.
*
* @return a list of block IDs
*/
public List getBlockIds() {
return new ArrayList(mBlockIdToBlockMap.keySet());
}
/**
* Returns the list of blocks stored in this dir.
*
* @return a list of blocks
*/
public List 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 BlockMeta from this storage dir by its block ID.
*
* @param blockId the block ID
* @return 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 BlockMeta from this storage dir by its block ID.
*
* @param blockId the block ID
* @return TempBlockMeta of the given block or null
* @throws BlockDoesNotExistException if no temp block is found
*/
public TempBlockMeta getTempBlockMeta(long blockId) throws BlockDoesNotExistException {
TempBlockMeta tempBlockMeta = mBlockIdToTempBlockMap.get(blockId);
if (tempBlockMeta == null) {
throw new BlockDoesNotExistException(ExceptionMessage.TEMP_BLOCK_META_NOT_FOUND, blockId);
}
return tempBlockMeta;
}
/**
* Adds the metadata of a new block into this storage dir.
*
* @param blockMeta the meta data 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) {
StorageLevelAlias alias =
StorageLevelAlias.getAlias(blockMeta.getBlockLocation().tierAlias());
throw new WorkerOutOfSpaceException(ExceptionMessage.NO_SPACE_FOR_BLOCK_META, blockId,
blockSize, getAvailableBytes(), alias);
}
if (hasBlockMeta(blockId)) {
StorageLevelAlias alias =
StorageLevelAlias.getAlias(blockMeta.getBlockLocation().tierAlias());
throw new BlockAlreadyExistsException(ExceptionMessage.ADD_EXISTING_BLOCK, blockId, alias);
}
mBlockIdToBlockMap.put(blockId, blockMeta);
reserveSpace(blockSize, true);
}
/**
* Adds the metadata of a new block into this storage dir.
*
* @param tempBlockMeta the meta data 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) {
StorageLevelAlias alias =
StorageLevelAlias.getAlias(tempBlockMeta.getBlockLocation().tierAlias());
throw new WorkerOutOfSpaceException(ExceptionMessage.NO_SPACE_FOR_BLOCK_META, blockId,
blockSize, getAvailableBytes(), alias);
}
if (hasTempBlockMeta(blockId)) {
StorageLevelAlias alias =
StorageLevelAlias.getAlias(tempBlockMeta.getBlockLocation().tierAlias());
throw new BlockAlreadyExistsException(ExceptionMessage.ADD_EXISTING_BLOCK, blockId, alias);
}
mBlockIdToTempBlockMap.put(blockId, tempBlockMeta);
Set 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 meta data 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 meta data 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 sessionBlocks = mSessionIdToTempBlockIdsMap.get(sessionId);
if (sessionBlocks == null || !sessionBlocks.contains(blockId)) {
StorageLevelAlias alias = StorageLevelAlias.getAlias(this.getDirIndex());
throw new BlockDoesNotExistException(ExceptionMessage.BLOCK_NOT_FOUND_FOR_SESSION, blockId,
alias, 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 meta data 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 meta data 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 tempBlockIds) {
Set 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 " + sessionId + " after cleanup.");
}
}
/**
* Gets the temporary blocks associated with a session in this StorageDir, an empty list is
* returned if the session has no temporary blocks in this StorageDir.
*
* @param sessionId the ID of the session
* @return A list of temporary blocks the session is associated with in this StorageDir
*/
public List getSessionTempBlocks(long sessionId) {
Set sessionTempBlockIds = mSessionIdToTempBlockIdsMap.get(sessionId);
if (sessionTempBlockIds == null || sessionTempBlockIds.isEmpty()) {
return Collections.emptyList();
}
List 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(), mTier.getTierLevel(), 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);
}
}
}