package org.dcache.pool.migration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import diskCacheV111.pools.PoolV2Mode; import diskCacheV111.util.CacheException; import diskCacheV111.util.CacheFileAvailable; import diskCacheV111.util.DiskErrorCacheException; import diskCacheV111.util.LockedCacheException; import diskCacheV111.util.PnfsId; import diskCacheV111.vehicles.Message; import dmg.cells.nucleus.AbstractCellComponent; import dmg.cells.nucleus.CellInfoProvider; import dmg.cells.nucleus.CellMessage; import dmg.cells.nucleus.CellMessageReceiver; import dmg.cells.nucleus.CellPath; import org.dcache.pool.classic.ChecksumModule; import org.dcache.pool.p2p.P2PClient; import org.dcache.pool.repository.ReplicaState; import org.dcache.pool.repository.IllegalTransitionException; import org.dcache.pool.repository.ReplicaDescriptor; import org.dcache.pool.repository.Repository; import org.dcache.pool.repository.Repository.OpenFlags; import org.dcache.pool.repository.StickyRecord; import org.dcache.vehicles.FileAttributes; import static org.dcache.pool.repository.ReplicaState.CACHED; import static org.dcache.pool.repository.ReplicaState.PRECIOUS; /** * Server component of migration module. * * This class is essentially a migration module specific frontend to * the pool to pool transfer component. * * The following messages are handled: * * - PoolMigrationUpdateReplicaMessage * - PoolMigrationCopyReplicaMessage * - PoolMigrationPingMessage * - PoolMigrationCancelMessage */ public class MigrationModuleServer extends AbstractCellComponent implements CellMessageReceiver, CellInfoProvider { private static final Logger _log = LoggerFactory.getLogger(MigrationModuleServer.class); private final Map<UUID, Request> _requests = new ConcurrentHashMap<>(); private P2PClient _p2p; private Repository _repository; private ExecutorService _executor; private ChecksumModule _checksumModule; private MigrationModule _migration; private PoolV2Mode _poolMode; public void setExecutor(ExecutorService executor) { _executor = executor; } public void setPPClient(P2PClient p2p) { _p2p = p2p; } public void setRepository(Repository repository) { _repository = repository; } public synchronized void setChecksumModule(ChecksumModule csm) { _checksumModule = csm; } public void setMigrationModule(MigrationModule migration) { _migration = migration; } public void setPoolMode(PoolV2Mode mode) { _poolMode = mode; } public Message messageArrived(CellMessage envelope, PoolMigrationCopyReplicaMessage message) throws CacheException, IOException, InterruptedException { if (message.isReply()) { return null; } if (_poolMode.isDisabled(PoolV2Mode.DISABLED_P2P_CLIENT)) { throw new CacheException(CacheException.POOL_DISABLED, "Pool is disabled"); } /* This check prevents updates that are indirectly triggered * by a local migration task: In particular the case in which * two pools each try to move the same files to each other * would otherwise have a race condition that would cause * files to be lost. This check prevents that any local * migration task is active on this file at this time and * hence the update request cannot be a result of a local * migration task. */ if (_migration.isActive(message.getPnfsId())) { throw new LockedCacheException("Target file is busy"); } Request request = new Request(envelope.getSourcePath().clone(), message); _requests.put(request.getUUID(), request); request.start(); message.setSucceeded(); return message; } public Message messageArrived(PoolMigrationPingMessage message) { if (message.isReply()) { return null; } UUID uuid = message.getUUID(); if (_requests.containsKey(uuid)) { message.setSucceeded(); } else { message.setFailed(CacheException.INVALID_ARGS, "No such request"); } return message; } public Message messageArrived(PoolMigrationCancelMessage message) throws CacheException { if (message.isReply()) { return null; } UUID uuid = message.getUUID(); Request request = _requests.get(uuid); if (request == null || !request.cancel()) { throw new CacheException(CacheException.INVALID_ARGS, "No such request"); } message.setSucceeded(); return message; } private class Request implements CacheFileAvailable, Runnable { private final CellPath _requestor; private final UUID _uuid; private final PnfsId _pnfsId; private final FileAttributes _fileAttributes; private final List<StickyRecord> _stickyRecords; private final ReplicaState _targetState; private final String _pool; private final boolean _computeChecksumOnUpdate; private final boolean _forceSourceMode; private final Long _atime; private final boolean _isMetaOnly; private Integer _companion; private Future<?> _updateTask; public Request(CellPath requestor, PoolMigrationCopyReplicaMessage message) { _requestor = requestor; _pnfsId = message.getPnfsId(); _fileAttributes = message.getFileAttributes(); _stickyRecords = message.getStickyRecords(); _targetState = message.getState(); _pool = message.getPool(); _computeChecksumOnUpdate = message.getComputeChecksumOnUpdate(); _forceSourceMode = message.isForceSourceMode(); _atime = message.getAtime(); _isMetaOnly = message.isMetaOnly(); if (_targetState != PRECIOUS && _targetState != CACHED) { throw new IllegalArgumentException("State must be either CACHED or PRECIOUS"); } /* Old pools don't provide a UUID so we create one * ourselves. Will eventually be removed. */ _uuid = (message.getUUID() != null) ? message.getUUID() : UUID.randomUUID(); } public synchronized UUID getUUID() { return _uuid; } public synchronized String getPool() { return _pool; } public synchronized void start() throws IOException, CacheException, InterruptedException { ReplicaState state = _repository.getState(_pnfsId); if (state == ReplicaState.NEW) { if (_isMetaOnly) { throw new CacheException(CacheException.FILE_NOT_IN_REPOSITORY, "Pool does not contain " + _pnfsId); } _companion = _p2p.newCompanion(_pool, _fileAttributes, _targetState, _stickyRecords, this, _forceSourceMode, _atime); } else { _updateTask = _executor.submit(this); } } public synchronized boolean cancel() { if (_companion != null) { return _p2p.cancel(_companion); } else if (_updateTask != null && _updateTask.cancel(true)) { if (_requests.remove(_uuid) != null) { finished(new CacheException("Task was cancelled")); } return true; } return false; } private synchronized void disableInterrupt() { _updateTask = null; } protected synchronized void finished(Throwable e) { PoolMigrationCopyFinishedMessage message = new PoolMigrationCopyFinishedMessage(_uuid, _pool, _pnfsId); if (e != null) { if (e instanceof CacheException) { CacheException ce = (CacheException) e; message.setFailed(ce.getRc(), ce.getMessage()); } else { message.setFailed(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e); } } sendMessage(new CellMessage(_requestor.revert(), message)); } @Override public synchronized void cacheFileAvailable(PnfsId pnfsId, Throwable e) { _requests.remove(_uuid); finished(e); } /** * Executed to update an existing replica */ @Override public void run() { try { if (_computeChecksumOnUpdate) { ReplicaDescriptor handle = _repository.openEntry(_pnfsId, EnumSet.of(OpenFlags.NOATIME)); try { _checksumModule.verifyChecksum(handle); } finally { handle.close(); } } disableInterrupt(); ReplicaState state = _repository.getState(_pnfsId); switch (state) { case CACHED: if (_targetState == PRECIOUS) { _repository.setState(_pnfsId, PRECIOUS); } // fall through case PRECIOUS: for (StickyRecord record: _stickyRecords) { _repository.setSticky(_pnfsId, record.owner(), record.expire(), false); } finished(null); break; default: finished(new CacheException("Cannot update file in state " + state)); break; } } catch (IOException e) { finished(new DiskErrorCacheException("I/O error during checksum calculation: " + e.getMessage())); } catch (InterruptedException e) { finished(new CacheException("Task was cancelled")); } catch (IllegalTransitionException e) { finished(new CacheException("Cannot update file in state " + e.getSourceState())); } catch (CacheException | NoSuchAlgorithmException | RuntimeException e) { finished(e); } finally { _requests.remove(_uuid); } } } }