/*
* 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;
import alluxio.Constants;
import alluxio.RpcUtils;
import alluxio.RpcUtils.RpcCallable;
import alluxio.RpcUtils.RpcCallableThrowsIOException;
import alluxio.Sessions;
import alluxio.StorageTierAssoc;
import alluxio.WorkerStorageTierAssoc;
import alluxio.exception.AlluxioException;
import alluxio.exception.BlockDoesNotExistException;
import alluxio.exception.UnexpectedAlluxioException;
import alluxio.exception.WorkerOutOfSpaceException;
import alluxio.thrift.AlluxioTException;
import alluxio.thrift.BlockWorkerClientService;
import alluxio.thrift.LockBlockResult;
import alluxio.thrift.LockBlockStatus;
import alluxio.thrift.LockBlockTOptions;
import alluxio.worker.block.options.OpenUfsBlockOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
/**
* This class is a Thrift handler for block worker RPCs invoked by an Alluxio client.
*/
@NotThreadSafe // TODO(jiri): make thread-safe (c.f. ALLUXIO-1624)
public final class BlockWorkerClientServiceHandler implements BlockWorkerClientService.Iface {
private static final Logger LOG = LoggerFactory.getLogger(BlockWorkerClientServiceHandler.class);
/** Block Worker handle that carries out most of the operations. */
private final BlockWorker mWorker;
/** Association between storage tier aliases and ordinals ond this worker. */
private final StorageTierAssoc mStorageTierAssoc;
/**
* Creates a new instance of {@link BlockWorkerClientServiceHandler}.
*
* @param worker block worker handler
*/
public BlockWorkerClientServiceHandler(BlockWorker worker) {
mWorker = worker;
mStorageTierAssoc = new WorkerStorageTierAssoc();
}
@Override
public long getServiceVersion() {
return Constants.BLOCK_WORKER_CLIENT_SERVICE_VERSION;
}
/**
* This should be called whenever a client does a direct read in order to update the worker's
* components that may care about the access times of the blocks (for example, Evictor, UI).
*
* @param blockId the id of the block to access
* @throws AlluxioTException if an Alluxio error occurs
*/
@Override
public void accessBlock(final long blockId) throws AlluxioTException {
RpcUtils.callAndLog(LOG, new RpcCallable<Void>() {
@Override
public Void call() throws AlluxioException {
mWorker.accessBlock(Sessions.ACCESS_BLOCK_SESSION_ID, blockId);
return null;
}
@Override
public String toString() {
return String.format("AccessBlock: blockId=%s", blockId);
}
});
}
/**
* Used to cache a block into Alluxio space, worker will move the temporary block file from
* session folder to data folder, and update the space usage information related. then update the
* block information to master.
*
* @param sessionId the id of the client requesting the commit
* @param blockId the id of the block to commit
* @throws AlluxioTException if an error occurs
*/
@Override
public void cacheBlock(final long sessionId, final long blockId)
throws AlluxioTException {
RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<Void>() {
@Override
public Void call() throws AlluxioException, IOException {
mWorker.commitBlock(sessionId, blockId);
return null;
}
@Override
public String toString() {
return String.format("CacheBlock: sessionId=%s, blockId=%s", sessionId, blockId);
}
});
}
/**
* Used to cancel a block which is being written. worker will delete the temporary block file and
* the location and space information related, then reclaim space allocated to the block.
*
* @param sessionId the id of the client requesting the abort
* @param blockId the id of the block to be aborted
* @throws AlluxioTException if an error occurs
*/
@Override
public void cancelBlock(final long sessionId, final long blockId)
throws AlluxioTException {
RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<Void>() {
@Override
public Void call() throws AlluxioException, IOException {
mWorker.abortBlock(sessionId, blockId);
return null;
}
@Override
public String toString() {
return String.format("CancelBlock: sessionId=%s, blockId=%s", sessionId, blockId);
}
});
}
/**
* Locks the file in Alluxio's space while the session is reading it.
*
* @param blockId the id of the block to be locked
* @param sessionId the id of the session
* @return the path of the block file locked
* @throws AlluxioTException if an error occurs
*/
@Override
public LockBlockResult lockBlock(final long blockId, final long sessionId,
final LockBlockTOptions options) throws AlluxioTException {
return RpcUtils.callAndLog(LOG, new RpcCallable<LockBlockResult>() {
@Override
public LockBlockResult call() throws AlluxioException {
if (!options.isSetUfsPath() || options.getUfsPath().isEmpty()) {
long lockId = mWorker.lockBlock(sessionId, blockId);
return new LockBlockResult(lockId, mWorker.readBlock(sessionId, blockId, lockId),
LockBlockStatus.ALLUXIO_BLOCK_LOCKED);
}
long lockId = mWorker.lockBlockNoException(sessionId, blockId);
if (lockId != BlockLockManager.INVALID_LOCK_ID) {
return new LockBlockResult(lockId, mWorker.readBlock(sessionId, blockId, lockId),
LockBlockStatus.ALLUXIO_BLOCK_LOCKED);
}
// When the block does not exist in Alluxio but exists in UFS, try to open the UFS
// block.
LockBlockStatus status;
if (mWorker.openUfsBlock(sessionId, blockId, new OpenUfsBlockOptions(options))) {
status = LockBlockStatus.UFS_TOKEN_ACQUIRED;
} else {
status = LockBlockStatus.UFS_TOKEN_NOT_ACQUIRED;
}
return new LockBlockResult(lockId, null, status);
}
@Override
public String toString() {
return String
.format("LockBlock: sessionId=%s, blockId=%s, options=%s", sessionId, blockId, options);
}
});
}
/**
* Used to promote block on under storage layer to top storage layer when there are more than one
* storage layers in Alluxio's space.
* otherwise.
*
* @param blockId the id of the block to move to the top layer
* @return true if the block is successfully promoted, otherwise false
* @throws AlluxioTException if an error occurs
*/
// TODO(calvin): This may be better as void.
@Override
public boolean promoteBlock(final long blockId) throws AlluxioTException {
return RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<Boolean>() {
@Override
public Boolean call() throws AlluxioException, IOException {
// TODO(calvin): Make the top level configurable.
mWorker.moveBlock(Sessions.MIGRATE_DATA_SESSION_ID, blockId, mStorageTierAssoc.getAlias(0));
return true;
}
@Override
public String toString() {
return String.format("PromoteBlock: blockId=%s", blockId);
}
});
}
/**
* Used to remove a block in Alluxio storage. Worker will delete the block file and
* reclaim space allocated to the block.
*
* @param blockId the id of the block to be removed
* @throws AlluxioTException if an error occurs
*/
@Override
public void removeBlock(final long blockId) throws AlluxioTException {
RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<Void>() {
@Override
public Void call() throws AlluxioException, IOException {
mWorker.removeBlock(Sessions.MIGRATE_DATA_SESSION_ID, blockId);
return null;
}
@Override
public String toString() {
return String.format("RemoveBlock: blockId=%s", blockId);
}
});
}
/**
* Used to allocate location and space for a new coming block, worker will choose the appropriate
* storage directory which fits the initial block size by some allocation strategy. If there is
* not enough space on Alluxio storage {@link alluxio.exception.WorkerOutOfSpaceException} will be
* thrown, if the file is already being written by the session,
* {@link alluxio.exception.FileAlreadyExistsException} will be thrown.
*
* @param sessionId the id of the client requesting the create
* @param blockId the id of the new block to create
* @param initialBytes the initial number of bytes to allocate for this block
* @param writeTier policy used to choose tier for this block
* @return the temporary file path of the block file
* @throws AlluxioTException if an error occurs
*/
@Override
public String requestBlockLocation(final long sessionId, final long blockId,
final long initialBytes, final int writeTier) throws AlluxioTException {
return RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<String>() {
@Override
public String call() throws AlluxioException, IOException {
return mWorker.createBlock(sessionId, blockId, mStorageTierAssoc.getAlias(writeTier),
initialBytes);
}
@Override
public String toString() {
return String.format("RequestBlockLocation: sessionId=%s, blockId=%s, initialBytes=%s, "
+ "writeTier=%s", sessionId, blockId, initialBytes, writeTier);
}
});
}
/**
* Requests space for a block.
*
* @param sessionId the id of the client requesting space
* @param blockId the id of the block to add the space to, this must be a temporary block
* @param requestBytes the amount of bytes to add to the block
* @return true if the worker successfully allocates space for the block on block’s location,
* false if there is not enough space
* @throws AlluxioTException if an error occurs
*/
@Override
public boolean requestSpace(final long sessionId, final long blockId, final long requestBytes)
throws AlluxioTException {
return RpcUtils.callAndLog(LOG, new RpcCallable<Boolean>() {
@Override
public Boolean call() throws AlluxioException {
try {
mWorker.requestSpace(sessionId, blockId, requestBytes);
return true;
} catch (WorkerOutOfSpaceException e) {
LOG.warn("Worker is out of space, failed to serve request for {} bytes for block {}",
requestBytes, blockId);
return false;
} catch (IOException e) {
LOG.error("Failed to serve request for {} bytes for block: {}", requestBytes, blockId, e);
// We must wrap IOException in an AlluxioException here for backwards compatibility with
// previous versions of our API.
throw new UnexpectedAlluxioException(e);
} catch (BlockDoesNotExistException e) {
LOG.error("Failed to serve request for {} bytes for block: {}", requestBytes, blockId, e);
throw e;
}
}
@Override
public String toString() {
return String.format("RequestSpace: sessionId=%s, blockId=%s, requestBytes=%s", sessionId,
blockId, requestBytes);
}
});
}
/**
* Used to unlock a block after the block is accessed, if the block is to be removed, delete the
* block file.
*
* @param blockId the id of the block to unlock
* @param sessionId the id of the client requesting the unlock
* @return true if successfully unlock the block, return false if the block is not
* found or failed to delete the block
* @throws AlluxioTException if an error occurs
*/
@Override
public boolean unlockBlock(final long blockId, final long sessionId)
throws AlluxioTException {
return RpcUtils.callAndLog(LOG, new RpcCallableThrowsIOException<Boolean>() {
@Override
public Boolean call() throws AlluxioException, IOException {
if (!mWorker.unlockBlock(sessionId, blockId)) {
mWorker.closeUfsBlock(sessionId, blockId);
}
return true;
}
@Override
public String toString() {
return String.format("UnlockBlock: sessionId=%s, blockId=%s", sessionId, blockId);
}
});
}
/**
* Local session send heartbeat to local worker to keep its temporary folder.
*
* @param sessionId the id of the client heartbeating
* @param metrics deprecated
*/
@Override
public void sessionHeartbeat(final long sessionId, final List<Long> metrics)
throws AlluxioTException {
RpcUtils.call(LOG, new RpcCallable<Void>() {
@Override
public Void call() throws AlluxioException {
mWorker.sessionHeartbeat(sessionId);
return null;
}
});
}
}