/*
* 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.master.file.meta;
import alluxio.Constants;
import alluxio.exception.BlockInfoException;
import alluxio.exception.FileAlreadyCompletedException;
import alluxio.exception.InvalidFileSizeException;
import alluxio.master.ProtobufUtils;
import alluxio.master.block.BlockId;
import alluxio.master.file.options.CreateFileOptions;
import alluxio.proto.journal.File.InodeFileEntry;
import alluxio.proto.journal.Journal.JournalEntry;
import alluxio.wire.FileInfo;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Alluxio file system's file representation in the file system master. The inode must be locked
* ({@link #lockRead()} or {@link #lockWrite()}) before methods are called.
*/
@NotThreadSafe
public final class InodeFile extends Inode<InodeFile> {
private List<Long> mBlocks;
private long mBlockContainerId;
private long mBlockSizeBytes;
private boolean mCacheable;
private boolean mCompleted;
private long mLength;
/**
* Creates a new instance of {@link InodeFile}.
*
* @param blockContainerId the block container id to use
*/
private InodeFile(long blockContainerId) {
super(BlockId.createBlockId(blockContainerId, BlockId.getMaxSequenceNumber()), false);
mBlocks = new ArrayList<>(1);
mBlockContainerId = blockContainerId;
mBlockSizeBytes = 0;
mCacheable = false;
mCompleted = false;
mLength = 0;
}
@Override
protected InodeFile getThis() {
return this;
}
@Override
public FileInfo generateClientFileInfo(String path) {
FileInfo ret = new FileInfo();
// note: in-memory percentage is NOT calculated here, because it needs blocks info stored in
// block master
ret.setFileId(getId());
ret.setName(getName());
ret.setPath(path);
ret.setLength(getLength());
ret.setBlockSizeBytes(getBlockSizeBytes());
ret.setCreationTimeMs(getCreationTimeMs());
ret.setCacheable(isCacheable());
ret.setFolder(isDirectory());
ret.setPinned(isPinned());
ret.setCompleted(isCompleted());
ret.setPersisted(isPersisted());
ret.setBlockIds(getBlockIds());
ret.setLastModificationTimeMs(getLastModificationTimeMs());
ret.setTtl(mTtl);
ret.setTtlAction(mTtlAction);
ret.setOwner(getOwner());
ret.setGroup(getGroup());
ret.setMode(getMode());
ret.setPersistenceState(getPersistenceState().toString());
ret.setMountPoint(false);
return ret;
}
/**
* Resets the file inode.
*/
public void reset() {
mBlocks = new ArrayList<>();
mLength = 0;
mCompleted = false;
mCacheable = false;
}
/**
* @return a duplication of all the block ids of the file
*/
public List<Long> getBlockIds() {
return new ArrayList<>(mBlocks);
}
/**
* @return the block size in bytes
*/
public long getBlockSizeBytes() {
return mBlockSizeBytes;
}
/**
* @return the length of the file in bytes. This is not accurate before the file is closed
*/
public long getLength() {
return mLength;
}
/**
* @return the id of a new block of the file
*/
public long getNewBlockId() {
long blockId = BlockId.createBlockId(mBlockContainerId, mBlocks.size());
// TODO(gene): Check for max block sequence number, and sanity check the sequence number.
// TODO(gene): Check isComplete?
// TODO(gene): This will not work with existing lineage implementation, since a new writer will
// not be able to get the same block ids (to write the same block ids).
mBlocks.add(blockId);
return blockId;
}
/**
* Gets the block id for a given index.
*
* @param blockIndex the index to get the block id for
* @return the block id for the index
* @throws BlockInfoException if the index of the block is out of range
*/
public long getBlockIdByIndex(int blockIndex) throws BlockInfoException {
if (blockIndex < 0 || blockIndex >= mBlocks.size()) {
throw new BlockInfoException(
"blockIndex " + blockIndex + " is out of range. File blocks: " + mBlocks.size());
}
return mBlocks.get(blockIndex);
}
/**
* @return true if the file is cacheable, false otherwise
*/
public boolean isCacheable() {
return mCacheable;
}
/**
* @return true if the file is complete, false otherwise
*/
public boolean isCompleted() {
return mCompleted;
}
/**
* @param blockSizeBytes the block size to use
* @return the updated object
*/
public InodeFile setBlockSizeBytes(long blockSizeBytes) {
Preconditions.checkArgument(blockSizeBytes >= 0, "Block size cannot be negative");
mBlockSizeBytes = blockSizeBytes;
return getThis();
}
/**
* @param blockIds the id's of the block
* @return the updated object
*/
public InodeFile setBlockIds(List<Long> blockIds) {
mBlocks = new ArrayList<>(Preconditions.checkNotNull(blockIds));
return getThis();
}
/**
* @param cacheable the cacheable flag value to use
* @return the updated object
*/
public InodeFile setCacheable(boolean cacheable) {
// TODO(gene). This related logic is not complete right. Fix this.
mCacheable = cacheable;
return getThis();
}
/**
* @param completed the complete flag value to use
* @return the updated object
*/
public InodeFile setCompleted(boolean completed) {
mCompleted = completed;
return getThis();
}
/**
* @param length the length to use
* @return the updated object
*/
public InodeFile setLength(long length) {
mLength = length;
return getThis();
}
/**
* Completes the file. Cannot set the length if the file is already completed. However, an unknown
* file size, {@link Constants#UNKNOWN_SIZE}, is valid. Cannot complete an already complete file,
* unless the completed length was previously {@link Constants#UNKNOWN_SIZE}.
*
* @param length The new length of the file, cannot be negative, but can be
* {@link Constants#UNKNOWN_SIZE}
* @throws InvalidFileSizeException if invalid file size is encountered
* @throws FileAlreadyCompletedException if the file is already completed
*/
public void complete(long length)
throws InvalidFileSizeException, FileAlreadyCompletedException {
if (mCompleted && mLength != Constants.UNKNOWN_SIZE) {
throw new FileAlreadyCompletedException("File " + getName() + " has already been completed.");
}
if (length < 0 && length != Constants.UNKNOWN_SIZE) {
throw new InvalidFileSizeException(
"File " + getName() + " cannot have negative length: " + length);
}
mCompleted = true;
mLength = length;
mBlocks.clear();
if (length == Constants.UNKNOWN_SIZE) {
// TODO(gpang): allow unknown files to be multiple blocks.
// If the length of the file is unknown, only allow 1 block to the file.
length = mBlockSizeBytes;
}
while (length > 0) {
long blockSize = Math.min(length, mBlockSizeBytes);
getNewBlockId();
length -= blockSize;
}
}
@Override
public String toString() {
return toStringHelper()
.add("blocks", mBlocks)
.add("blockContainerId", mBlockContainerId)
.add("blockSizeBytes", mBlockSizeBytes)
.add("cacheable", mCacheable)
.add("completed", mCompleted)
.add("length", mLength).toString();
}
/**
* Converts the entry to an {@link InodeFile}.
*
* @param entry the entry to convert
* @return the {@link InodeFile} representation
*/
public static InodeFile fromJournalEntry(InodeFileEntry entry) {
return new InodeFile(BlockId.getContainerId(entry.getId()))
.setName(entry.getName())
.setBlockIds(entry.getBlocksList())
.setBlockSizeBytes(entry.getBlockSizeBytes())
.setCacheable(entry.getCacheable())
.setCompleted(entry.getCompleted())
.setCreationTimeMs(entry.getCreationTimeMs())
.setLastModificationTimeMs(entry.getLastModificationTimeMs(), true)
.setLength(entry.getLength())
.setParentId(entry.getParentId())
.setPersistenceState(PersistenceState.valueOf(entry.getPersistenceState()))
.setPinned(entry.getPinned())
.setTtl(entry.getTtl())
.setTtlAction((ProtobufUtils.fromProtobuf(entry.getTtlAction())))
.setOwner(entry.getOwner())
.setGroup(entry.getGroup())
.setMode((short) entry.getMode());
}
/**
* Creates an {@link InodeFile}.
*
* @param blockContainerId block container id of this inode
* @param parentId id of the parent of this inode
* @param name name of this inode
* @param creationTimeMs the creation time for this inode
* @param options options to create this file
* @return the {@link InodeFile} representation
*/
public static InodeFile create(long blockContainerId, long parentId, String name,
long creationTimeMs, CreateFileOptions options) {
return new InodeFile(blockContainerId)
.setBlockSizeBytes(options.getBlockSizeBytes())
.setCreationTimeMs(creationTimeMs)
.setName(name)
.setTtl(options.getTtl())
.setTtlAction(options.getTtlAction())
.setParentId(parentId)
.setOwner(options.getOwner())
.setGroup(options.getGroup())
.setMode(options.getMode().toShort())
.setPersistenceState(options.isPersisted() ? PersistenceState.PERSISTED
: PersistenceState.NOT_PERSISTED);
}
@Override
public JournalEntry toJournalEntry() {
InodeFileEntry inodeFile = InodeFileEntry.newBuilder()
.addAllBlocks(getBlockIds())
.setBlockSizeBytes(getBlockSizeBytes())
.setCacheable(isCacheable())
.setCompleted(isCompleted())
.setCreationTimeMs(getCreationTimeMs())
.setGroup(getGroup())
.setId(getId())
.setLastModificationTimeMs(getLastModificationTimeMs())
.setLength(getLength())
.setMode(getMode())
.setName(getName())
.setOwner(getOwner())
.setParentId(getParentId())
.setPersistenceState(getPersistenceState().name())
.setPinned(isPinned())
.setTtl(getTtl())
.setTtlAction(ProtobufUtils.toProtobuf(getTtlAction())).build();
return JournalEntry.newBuilder().setInodeFile(inodeFile).build();
}
}