// $Id: P2PClient.java,v 1.21 2007-10-31 17:27:11 radicke Exp $ package org.dcache.pool.p2p; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import diskCacheV111.util.CacheException; import diskCacheV111.util.CacheFileAvailable; import diskCacheV111.util.PnfsId; import diskCacheV111.vehicles.DoorTransferFinishedMessage; import diskCacheV111.vehicles.HttpDoorUrlInfoMessage; import diskCacheV111.vehicles.HttpProtocolInfo; import dmg.cells.nucleus.AbstractCellComponent; import dmg.cells.nucleus.CellCommandListener; import dmg.cells.nucleus.CellInfoProvider; import dmg.cells.nucleus.CellMessageReceiver; import dmg.cells.nucleus.CellSetupProvider; import dmg.util.command.Argument; import dmg.util.command.Command; import org.dcache.cells.CellStub; import org.dcache.pool.classic.ChecksumModule; import org.dcache.pool.repository.ReplicaState; import org.dcache.pool.repository.Repository; import org.dcache.pool.repository.StickyRecord; import org.dcache.vehicles.FileAttributes; import static com.google.common.base.Preconditions.checkArgument; import static java.util.stream.Collectors.*; public class P2PClient extends AbstractCellComponent implements CellMessageReceiver, CellCommandListener, CellSetupProvider, CellInfoProvider { private final Map<Integer, Companion> _companions = new HashMap<>(); private ScheduledExecutorService _executor; private Repository _repository; private ChecksumModule _checksumModule; private CellStub _pnfs; private CellStub _pool; private InetAddress _interface; public synchronized void setExecutor(ScheduledExecutorService executor) { _executor = executor; } public synchronized void setRepository(Repository repository) { _repository = repository; } public synchronized void setChecksumModule(ChecksumModule csm) { _checksumModule = csm; } public synchronized void setPnfs(CellStub pnfs) { _pnfs = pnfs; } public synchronized void setPool(CellStub pool) { _pool = pool; } public synchronized int getActiveJobs() { return _companions.size(); } public synchronized void messageArrived(DoorTransferFinishedMessage message) { HttpProtocolInfo pinfo = (HttpProtocolInfo)message.getProtocolInfo(); int sessionId = pinfo.getSessionId(); Companion companion = _companions.get(sessionId); if (companion != null) { companion.messageArrived(message); } } public synchronized void messageArrived(HttpDoorUrlInfoMessage message) { int sessionId = (int) message.getId(); Companion companion = _companions.get(sessionId); if (companion != null) { companion.messageArrived(message); } else { /* The original p2p is no longer around, but maybe we can use the redirect * for another p2p transfer. */ PnfsId pnfsId = new PnfsId(message.getPnfsId()); for (Companion c : _companions.values()) { if (c.getPnfsId().equals(pnfsId)) { c.messageArrived(message); return; } } /* TODO: We should kill the mover, but at the moment we don't * know the mover id here. */ } } /** * Adds a companion to the _companions map. */ private synchronized int addCompanion(Companion companion) { int sessionId = companion.getId(); _companions.put(sessionId, companion); return sessionId; } /** * Removes a companion from the _companions map. */ private synchronized void removeCompanion(int sessionId) { _companions.remove(sessionId); notifyAll(); } /** * Cancels all companions for a given file. */ private synchronized void cancelCompanions(PnfsId pnfsId, String cause) { for (Companion companion: _companions.values()) { if (pnfsId.equals(companion.getPnfsId())) { companion.cancel(cause); } } } /** * Small wrapper for the real callback. Will remove the companion * from the <code>_companions</code> map. */ private class Callback implements CacheFileAvailable { private final CacheFileAvailable _callback; private int _id; Callback(CacheFileAvailable callback) { _callback = callback; _id = -1; } synchronized void setId(int id) { _id = id; notifyAll(); } synchronized int getId() throws InterruptedException { while (_id == -1) { wait(); } return _id; } @Override public void cacheFileAvailable(PnfsId pnfsId, Throwable t) { try { if (_callback != null) { _callback.cacheFileAvailable(pnfsId, t); } removeCompanion(getId()); /* In case of a successfull transfer, there is no * reason to keep other companions on the same file * around. */ if (t == null) { cancelCompanions(pnfsId, "Replica already exists"); } } catch (InterruptedException e) { // Ignored, typically happens at cell shutdown } } } public synchronized int newCompanion(String sourcePoolName, FileAttributes fileAttributes, ReplicaState targetState, List<StickyRecord> stickyRecords, CacheFileAvailable callback, boolean forceSourceMode, Long atime) throws IOException, CacheException, InterruptedException { if (getCellEndpoint() == null) { throw new IllegalStateException("Endpoint not initialized"); } if (_pool == null) { throw new IllegalStateException("Pool stub not initialized"); } if (_executor == null) { throw new IllegalStateException("Executor not initialized"); } if (_repository == null) { throw new IllegalStateException("Repository not initialized"); } if (_checksumModule == null) { throw new IllegalStateException("Checksum module not initialized"); } if (_pnfs == null) { throw new IllegalStateException("PNFS stub not initialized"); } if (_repository.getState(fileAttributes.getPnfsId()) != ReplicaState.NEW) { throw new IllegalStateException("Replica already exists"); } Callback cb = new Callback(callback); Companion companion = new Companion(_executor, _interface, _repository, _checksumModule, _pnfs, _pool, fileAttributes, sourcePoolName, getCellName(), getCellDomainName(), targetState, stickyRecords, cb, forceSourceMode, atime); int id = addCompanion(companion); cb.setId(id); return id; } /** * Cancels a transfer. Returns true if the transfer was * cancelled. Returns false if the transfer was already completed * or did not exist. */ public synchronized boolean cancel(int id) { Companion companion = _companions.get(id); return (companion != null) && companion.cancel("Transfer was cancelled"); } /** * Cancels all transfers. */ public synchronized void shutdown() throws InterruptedException { for (Companion companion: _companions.values()) { companion.cancel("Pool is going down"); } while (!_companions.isEmpty()) { wait(); } } @Override public synchronized void getInfo(PrintWriter pw) { if (_interface != null) { pw.println(" Interface : " + _interface); } } @Override public synchronized void printSetup(PrintWriter pw) { pw.println("#\n# Pool to Pool (P2P)\n#"); if (_interface != null) { pw.println("pp interface " + _interface.getHostAddress()); } } @Command(name="pp set pnfs timeout", hint = "Obsolete Command", description = "This command is obsolete.") public class PpSetPnfsTimeoutCommand implements Callable<String> { @Argument int timeout; @Override public String call() { return "This command is obsolete."; } } @Command(name="pp set max active") @Deprecated public class PpSetMaxActiveCommand implements Callable<String> { @Argument int maxActiveAllowed; @Override public String call() throws IllegalArgumentException { return ""; } } @Command(name = "pp set listen", hint = "Obsolete Command", description = "The command is Obsolete. Use 'pp interface' instead.") public class PpSetListenCommand implements Callable<String> { @Override public String call() { return "This command is obsolete. Use 'pp interface' instead."; } } @AffectsSetup @Command(name = "pp interface", hint = "Specifies the interface used when connecting to other pools.", description = "For pool to pool transfers, the destination creates a TCP " + "connection to the source pool. For this to work the source pool " + "must select one of its network interfaces to which the destination " + "pool can connect. For compatibility reasons this interface is " + "not specified explicitly on the source pool. Instead an interface " + "on the target pool is specified and the source pool selects an " + "interface facing the target interface.\n\n" + "If * is provided then an interface is selected automatically.") public class PpInterfaceCommand implements Callable<String> { @Argument(required = false, usage = "Specify the address to which the destination pool can connect.") String address; @Override public String call() throws UnknownHostException { synchronized (P2PClient.this) { if (address != null) { _interface = address.equals("*") ? null : InetAddress.getByName(address); } return "PP interface is " + ((_interface == null) ? "selected automatically" : _interface) + "."; } } } @Command(name = "pp get file", hint = "initiate pool-to-pool client transfer request of a file", description = "Transfer a file from a specified pool to this pool through " + "pool-to-pool client transfer request. The transferred file will " + "be marked cached.") public class PpGetFileCommand implements Callable<String> { @Argument(index = 0, usage = "Specify the pnfsID of the file to transfer.") PnfsId pnfsId; @Argument(index = 1, metaVar = "sourcePoolName", usage = "Specify the source pool name where the file reside.") String pool; @Override public String call() throws IOException, CacheException, InterruptedException { List<StickyRecord> stickyRecords = Collections.emptyList(); newCompanion(pool, FileAttributes.ofPnfsId(pnfsId), ReplicaState.CACHED, stickyRecords, null, false, null); return "Transfer Initiated"; } } @Command(name = "pp remove", hint = "cancel a pool-to-pool client transfer request", description = "Terminate a specific pool-to-pool client transfer request by " + "specifying the session ID. This stop the transfer from completion. " + "An error is thrown if the file session ID is not found and " + "this might be due to either the file transfer is completed or " + "the session ID doesn't exist at all.") public class PpRemoveCommand implements Callable<String> { @Argument(metaVar = "sessionID", usage = "Specify the session ID identifying the transfer.") int id; @Override public String call() throws IllegalArgumentException { if (!cancel(id)) { throw new IllegalArgumentException("Session ID not found: " + id); } return ""; } } @Command(name = "pp ls", hint = "list pool-to-pool client transfer request", description = "Get the list of all active and waiting pool-to-pool client " + "transfer request. The return list comprise of: the session ID " + "identifying the transfer; the pnfsID of the file; " + "and the state of the state machine (which is driving the transfer).") public class PpLsCommand implements Callable<String> { @Override public String call() { synchronized (P2PClient.this) { return _companions.values().stream().map(Object::toString).collect(joining("\n")); } } } }