/* * 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; } }); } }