/* dCache - http://www.dcache.org/ * * Copyright (C) 2013 - 2015 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.pool.movers; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.reflect.TypeToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import java.io.IOException; import java.io.InterruptedIOException; import java.nio.channels.CompletionHandler; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.Set; import diskCacheV111.util.CacheException; import diskCacheV111.util.ChecksumFactory; import diskCacheV111.util.DiskErrorCacheException; import diskCacheV111.vehicles.PoolAcceptFileMessage; import diskCacheV111.vehicles.PoolIoFileMessage; import diskCacheV111.vehicles.ProtocolInfo; import dmg.cells.nucleus.CellPath; import org.dcache.pool.classic.Cancellable; import org.dcache.pool.classic.ChecksumModule; import org.dcache.pool.classic.TransferService; import org.dcache.pool.repository.ReplicaDescriptor; import org.dcache.pool.repository.RepositoryChannel; import org.dcache.util.Checksum; import org.dcache.util.TryCatchTemplate; import org.dcache.vehicles.FileAttributes; import static com.google.common.base.Preconditions.checkArgument; /** * Abstract base class for movers. */ public abstract class AbstractMover<P extends ProtocolInfo, M extends AbstractMover<P, M>> implements Mover<P> { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMover.class); protected final long _id; protected final String _queue; protected final String _initiator; protected final boolean _isPoolToPoolTransfer; protected final CellPath _pathToDoor; protected final P _protocolInfo; protected final Subject _subject; protected final ReplicaDescriptor _handle; protected final IoMode _ioMode; protected final TransferService<M> _transferService; protected final String _billingPath; protected final String _transferPath; protected volatile int _errorCode; protected volatile String _errorMessage = ""; private final ChecksumFactory _checksumFactory; private volatile ChecksumChannel _checksumChannel; public AbstractMover(ReplicaDescriptor handle, PoolIoFileMessage message, CellPath pathToDoor, TransferService<M> transferService, ChecksumModule checksumModule) { TypeToken<M> type = new TypeToken<M>(getClass()) {}; checkArgument(type.isSupertypeOf(getClass())); _queue = message.getIoQueueName(); _protocolInfo = (P) message.getProtocolInfo(); _initiator = message.getInitiator(); _isPoolToPoolTransfer = message.isPool2Pool(); _ioMode = (message instanceof PoolAcceptFileMessage) ? IoMode.WRITE : IoMode.READ; _subject = message.getSubject(); _id = message.getId(); _billingPath = message.getBillingPath(); _transferPath = message.getTransferPath(); _pathToDoor = pathToDoor; _handle = handle; _transferService = transferService; _checksumFactory = getChecksumFactoryFor(checksumModule, handle); } @Override public FileAttributes getFileAttributes() { return _handle.getFileAttributes(); } @Override public P getProtocolInfo() { return _protocolInfo; } @Override public long getClientId() { return _id; } @Override public void setTransferStatus(int errorCode, String errorMessage) { if (_errorCode == 0) { _errorCode = errorCode; _errorMessage = Strings.nullToEmpty(errorMessage); } } @Override public String getQueueName() { return _queue; } @Override public int getErrorCode() { return _errorCode; } @Override public String getErrorMessage() { return _errorMessage; } @Override public String getInitiator() { return _initiator; } @Override public boolean isPoolToPoolTransfer() { return _isPoolToPoolTransfer; } @Override public ReplicaDescriptor getIoHandle() { return _handle; } @Override public IoMode getIoMode() { return _ioMode; } @Override public CellPath getPathToDoor() { return _pathToDoor; } @Override public String getBillingPath() { return _billingPath; } @Override public String getTransferPath() { return _transferPath; } @Override public Subject getSubject() { return _subject; } @Override @SuppressWarnings("unchecked") public void close(CompletionHandler<Void, Void> completionHandler) { _transferService.closeMover((M) this, completionHandler); } @Override public Cancellable execute(CompletionHandler<Void, Void> completionHandler) { return new TryCatchTemplate<Void, Void>(completionHandler) { @Override @SuppressWarnings("unchecked") public Cancellable executeWithCancellable() throws Exception { return _transferService.executeMover((M) AbstractMover.this, this); } @Override public synchronized void onFailure(Throwable t, Void attachment) { try { throw t; } catch (DiskErrorCacheException e) { LOGGER.error("Transfer failed due to a disk error: {}", e.toString()); setTransferStatus(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } catch (CacheException e) { LOGGER.error("Transfer failed: {}", e.getMessage()); setTransferStatus(e.getRc(), e.getMessage()); } catch (InterruptedIOException | InterruptedException e) { LOGGER.error("Transfer was forcefully killed"); setTransferStatus(CacheException.DEFAULT_ERROR_CODE, "Transfer was forcefully killed"); } catch (RuntimeException e) { LOGGER.error("Transfer failed due to a bug", e); setTransferStatus(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, "Bug detected (please report): " + e.getMessage()); } catch (Exception e) { LOGGER.error("Transfer failed: {}", e.toString()); setTransferStatus(CacheException.DEFAULT_ERROR_CODE, "General problem: " + e.getMessage()); } catch (Throwable e) { LOGGER.error("Transfer failed:", e); Thread thread = Thread.currentThread(); thread.getUncaughtExceptionHandler().uncaughtException(thread, e); setTransferStatus(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } }; } /** * Opens a RepositoryChannel for the replica being transferred by this mover. * * The caller is responsible for closing the stream at the end of the transfer. * * TODO: Consider moving this method to RepositoryChannel. * * @return An open RepositoryChannel to the replica of this mover * @throws DiskErrorCacheException If the file could not be opened */ public RepositoryChannel openChannel() throws DiskErrorCacheException { RepositoryChannel channel; try { channel = _handle.createChannel(); if (getIoMode() == IoMode.WRITE) { try { channel = _checksumChannel = new ChecksumChannel(channel, _checksumFactory); } catch (Throwable t) { /* This should only happen in case of JVM Errors or if the checksum digest cannot be * instantiated (which, barring bugs, should never happen). */ try { channel.close(); } catch (IOException e) { t.addSuppressed(e); } throw t; } } } catch (IOException e) { throw new DiskErrorCacheException( "File could not be opened; please check the file system: " + e.getMessage(), e); } return channel; } @Override public Set<Checksum> getActualChecksums() { return (_checksumChannel == null) ? Collections.emptySet() : Optional.fromNullable(_checksumChannel.getChecksum()).asSet(); } @Override public Set<Checksum> getExpectedChecksums() { return Collections.emptySet(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getFileAttributes().getPnfsId()); sb.append(" IoMode=").append(getIoMode()); sb.append(" h={").append(getStatus()).append("} bytes=").append(getBytesTransferred()).append( " time/sec=").append(getTransferTime() / 1000L).append(" LM="); long lastTransferTime = getLastTransferred(); if (lastTransferTime == 0L) { sb.append(0); } else { sb.append((System.currentTimeMillis() - lastTransferTime) / 1000L); } return sb.toString(); } private static ChecksumFactory getChecksumFactoryFor(ChecksumModule checksumModule, ReplicaDescriptor handle) { try { return checksumModule.getPreferredChecksumFactory(handle); } catch (NoSuchAlgorithmException | CacheException e) { LOGGER.error("Failed to instantiate mover due to unsupported checksum type: " + e.getMessage(), e); } return null; } protected abstract String getStatus(); }