package org.dcache.srm.shell; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import org.apache.axis.types.URI; import org.apache.axis.types.UnsignedLong; import javax.annotation.Nonnull; import java.io.File; import java.rmi.RemoteException; import java.util.Map; import java.util.concurrent.CancellationException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.dcache.srm.SRMException; import org.dcache.srm.v2_2.ArrayOfAnyURI; import org.dcache.srm.v2_2.ArrayOfString; import org.dcache.srm.v2_2.ArrayOfTGetFileRequest; import org.dcache.srm.v2_2.ArrayOfTGetRequestFileStatus; import org.dcache.srm.v2_2.ArrayOfTPutFileRequest; import org.dcache.srm.v2_2.ArrayOfTPutRequestFileStatus; import org.dcache.srm.v2_2.ArrayOfTSURLReturnStatus; import org.dcache.srm.v2_2.ISRM; import org.dcache.srm.v2_2.SrmAbortRequestRequest; import org.dcache.srm.v2_2.SrmAbortRequestResponse; import org.dcache.srm.v2_2.SrmPrepareToGetRequest; import org.dcache.srm.v2_2.SrmPrepareToGetResponse; import org.dcache.srm.v2_2.SrmPrepareToPutRequest; import org.dcache.srm.v2_2.SrmPrepareToPutResponse; import org.dcache.srm.v2_2.SrmPutDoneRequest; import org.dcache.srm.v2_2.SrmPutDoneResponse; import org.dcache.srm.v2_2.SrmReleaseFilesRequest; import org.dcache.srm.v2_2.SrmReleaseFilesResponse; import org.dcache.srm.v2_2.SrmStatusOfGetRequestRequest; import org.dcache.srm.v2_2.SrmStatusOfGetRequestResponse; import org.dcache.srm.v2_2.SrmStatusOfPutRequestRequest; import org.dcache.srm.v2_2.SrmStatusOfPutRequestResponse; import org.dcache.srm.v2_2.TAccessLatency; import org.dcache.srm.v2_2.TAccessPattern; import org.dcache.srm.v2_2.TConnectionType; import org.dcache.srm.v2_2.TGetFileRequest; import org.dcache.srm.v2_2.TGetRequestFileStatus; import org.dcache.srm.v2_2.TOverwriteMode; import org.dcache.srm.v2_2.TPutFileRequest; import org.dcache.srm.v2_2.TPutRequestFileStatus; import org.dcache.srm.v2_2.TRetentionPolicy; import org.dcache.srm.v2_2.TRetentionPolicyInfo; import org.dcache.srm.v2_2.TSURLReturnStatus; import org.dcache.srm.v2_2.TStatusCode; import org.dcache.srm.v2_2.TTransferParameters; import static com.google.common.base.Preconditions.checkNotNull; import static java.lang.Math.max; import static java.lang.Math.min; import static org.dcache.srm.shell.TStatusCodes.checkSuccess; /** * Support for transferring a file using SRM. */ public class SrmTransferAgent extends AbstractFileTransferAgent { private final ScheduledExecutorService _srmExecutor = Executors.newSingleThreadScheduledExecutor(); private ISRM srm; private FileTransferAgent agent; private ImmutableList<String> protocols; public void setSrm(ISRM srm) { this.srm = checkNotNull(srm); } public void setFileTransferAgent(FileTransferAgent agent) { this.agent = checkNotNull(agent); } @Nonnull @Override public Map<String,String> getOptions() { return agent.getOptions(); } /** * Alter an option. */ @Override public void setOption(String key, String value) { agent.setOption(key, value); } protected synchronized ImmutableList<String> getProtocolPreference() { if (protocols == null) { protocols = buildProtocolPreference(agent); } return protocols; } private static ImmutableList<String> buildProtocolPreference(FileTransferAgent agent) { Ordering<Map.Entry<String,Integer>> order = Ordering.natural(). onResultOf(new Function<Map.Entry<String,Integer>, Integer>(){ @Override public Integer apply(Map.Entry<String,Integer> input) { return input.getValue(); } }).reverse(); ImmutableList.Builder<String> builder = ImmutableList.builder(); for (Map.Entry<String,Integer> entry : order.sortedCopy(agent.getSupportedProtocols().entrySet())) { builder.add(entry.getKey()); } return builder.build(); } @Override public void close() throws Exception { MoreExecutors.shutdownAndAwaitTermination(_srmExecutor, 500, TimeUnit.MILLISECONDS); agent.close(); } @Override public FileTransfer download(URI source, File destination) { if (source.getScheme().equals("srm")) { GetTransfer transfer = new GetTransfer(source, destination); transfer.start(); return transfer; } return null; } @Override public FileTransfer upload(File source, URI destination) { if (destination.getScheme().equals("srm")) { PutTransfer transfer = new PutTransfer(source, destination); transfer.start(); return transfer; } return null; } @Override public String getTransportName() { return "srm"; } /** * Base class for uploads and downloads. */ abstract private class AbstractSRMTransfer extends AbstractFileTransfer { private final URI _surl; private final File _localFile; protected String _description = "awaiting initiation"; private String _token; private FileTransfer _transfer; AbstractSRMTransfer(URI surl, File localFile) { _surl = surl; _localFile = localFile; Futures.addCallback(this, new FutureCallback(){ @Override public void onSuccess(Object result) { } @Override public void onFailure(Throwable t) { if (t instanceof CancellationException) { onCancel(); } } }); } private void onCancel() { FileTransfer transfer = getTransfer(); if (transfer != null) { transfer.cancel(true); } else { abortRequest(); } } @Override public String getStatus() { StringBuilder sb = new StringBuilder(); sb.append(_surl).append(": "); if (_transfer == null) { sb.append(_description); } else { sb.append(_transfer.getStatus()); } return sb.toString(); } public ArrayOfString getProtocolPreference() { ImmutableList<String> protocols = SrmTransferAgent.this.getProtocolPreference(); return new ArrayOfString(protocols.toArray(new String[protocols.size()])); } public void setTransfer(FileTransfer transfer) { _transfer = transfer; } public FileTransfer getTransfer() { return _transfer; } protected void setRequestToken(String token) { _token = token; } public String getRequestToken() { return _token; } public URI getSurl() { return _surl; } public File getLocalFile() { return _localFile; } public void setDescription(String description) { _description = description; } protected void fail(RemoteException e) { setDescription("Problem contacting remote server : " + e.toString()); } protected void fail(SRMException e) { setDescription(e.getMessage() == null ? "Unspecified problem" : e.getMessage()); } protected void abortRequest() { String token = getRequestToken(); if (token != null) { try { SrmAbortRequestRequest request = new SrmAbortRequestRequest(token, null); SrmAbortRequestResponse response = srm.srmAbortRequest(request); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_SUCCESS); } catch (RemoteException e) { setDescription(_description + ", failed to abort request: " + e.getMessage()); } catch (SRMException e) { setDescription(_description + ", failed to abort request: " + "[" + e.getStatusCode() + "] " + e.getMessage()); } } } class FailOnBugTask implements Runnable { private final Runnable _inner; FailOnBugTask(Runnable inner) { _inner = inner; } @Override public void run() { try { _inner.run(); } catch (RuntimeException e) { setDescription("bug detected: " + e.toString()); e.printStackTrace(); } } } } /** * Class for handling downloads specifically. */ public class PutTransfer extends AbstractSRMTransfer { public PutTransfer(File source, URI target) { super(target, source); } private void start() { _srmExecutor.execute(new FailOnBugTask(new Runnable(){ @Override public void run() { prepareToPut(); } })); } private void prepareToPut() { setDescription("requesting transfer URL."); ArrayOfTPutFileRequest fileRequests = new ArrayOfTPutFileRequest(new TPutFileRequest[]{ new TPutFileRequest(getSurl(), new UnsignedLong(getLocalFile().length())), }); TTransferParameters params = new TTransferParameters(TAccessPattern.TRANSFER_MODE, TConnectionType.WAN, null, getProtocolPreference()); // FIXME: make target ALRP configurable TRetentionPolicyInfo retention = new TRetentionPolicyInfo(TRetentionPolicy.REPLICA, TAccessLatency.ONLINE); // FIXME: add user-description to allow rediscovery of ongoing requests // FIXME: add overwrite option // FIXME: add space-token option SrmPrepareToPutRequest request = new SrmPrepareToPutRequest( null, fileRequests, null, TOverwriteMode.NEVER, null, (int)TimeUnit.DAYS.toSeconds(1), null, null, null, null, retention, params); try { SrmPrepareToPutResponse response = srm.srmPrepareToPut(request); setRequestToken(response.getRequestToken()); TStatusCode requestStatus = response.getReturnStatus().getStatusCode(); if (requestStatus != TStatusCode.SRM_REQUEST_QUEUED && requestStatus != TStatusCode.SRM_REQUEST_INPROGRESS && requestStatus != TStatusCode.SRM_SUCCESS) { if (response.getArrayOfFileStatuses() != null) { TPutRequestFileStatus file = getFileStatus(response.getArrayOfFileStatuses()); throw new SRMException(file.getStatus().getExplanation()); } else { throw new SRMException(response.getReturnStatus().getExplanation()); } } TPutRequestFileStatus file = getFileStatus(response.getArrayOfFileStatuses()); TStatusCode requestStatusCode = response.getReturnStatus().getStatusCode(); if (requestStatusCode == TStatusCode.SRM_REQUEST_QUEUED || requestStatusCode == TStatusCode.SRM_REQUEST_INPROGRESS) { TStatusCode fileStatusCode = file.getStatus().getStatusCode(); if (fileStatusCode != requestStatusCode) { throw new SRMException("mismatch between file and response status: " + fileStatusCode + " != " + requestStatusCode); } setDescription("waiting to request status update."); Integer waitTime = file.getEstimatedWaitTime(); int delay = waitTime == null ? 1 : max(min(waitTime, 60), 1); _srmExecutor.schedule(new FailOnBugTask(new Runnable() { @Override public void run() { statusOfPrepareToPut(); } }), delay, TimeUnit.SECONDS); } else { checkSuccess(file.getStatus(), TStatusCode.SRM_SPACE_AVAILABLE); initiateTransfer(file.getTransferURL()); } } catch (RemoteException e) { fail(e); abortRequest(); failed(e); } catch (SRMException e) { fail(e); abortRequest(); failed(e); } } private void statusOfPrepareToPut() { setDescription("request status update."); SrmStatusOfPutRequestRequest request = new SrmStatusOfPutRequestRequest(getRequestToken(), null, new ArrayOfAnyURI(new URI[]{getSurl()})); try { SrmStatusOfPutRequestResponse response = srm.srmStatusOfPutRequest(request); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_REQUEST_QUEUED, TStatusCode.SRM_REQUEST_INPROGRESS, TStatusCode.SRM_SUCCESS); TStatusCode requestStatusCode = response.getReturnStatus().getStatusCode(); TPutRequestFileStatus file = getFileStatus(response.getArrayOfFileStatuses()); if (requestStatusCode == TStatusCode.SRM_REQUEST_QUEUED || requestStatusCode == TStatusCode.SRM_REQUEST_INPROGRESS) { TStatusCode fileStatusCode = file.getStatus().getStatusCode(); if (fileStatusCode != requestStatusCode) { throw new SRMException("mismatch between file and response status: " + fileStatusCode + " != " + requestStatusCode); } setDescription("waiting to request status update."); Integer waitTime = file.getEstimatedWaitTime(); int delay = waitTime == null ? 1 : Math.max(Math.min(waitTime, 60), 1); _srmExecutor.schedule(new FailOnBugTask(new Runnable() { @Override public void run() { statusOfPrepareToPut(); } }), delay, TimeUnit.SECONDS); } else { checkSuccess(file.getStatus(), TStatusCode.SRM_SPACE_AVAILABLE); initiateTransfer(file.getTransferURL()); } } catch (RemoteException e) { fail(e); abortRequest(); failed(e); } catch (SRMException e) { fail(e); abortRequest(); failed(e); } } private void initiateTransfer(URI turl) { FileTransfer transfer = agent.upload(getLocalFile(), turl); if (transfer == null) { setDescription("unable to transfer from " + turl); abortRequest(); } else { setDescription("preparing for transfer using " + turl); setTransfer(transfer); Futures.addCallback(transfer, new FutureCallback<Void>(){ @Override public void onSuccess(Void result) { setDescription("file transfer complete."); setTransfer(null); putDone(); succeeded(); } @Override public void onFailure(Throwable t) { setDescription(t.getMessage()); setTransfer(null); abortRequest(); failed(t); } }, _srmExecutor); } } private void putDone() { SrmPutDoneRequest request = new SrmPutDoneRequest(getRequestToken(), null, new ArrayOfAnyURI(new URI[]{getSurl()})); try { SrmPutDoneResponse response = srm.srmPutDone(request); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_SUCCESS); TSURLReturnStatus status = getSURLStatus(response.getArrayOfFileStatuses()); checkSuccess(status.getStatus(), TStatusCode.SRM_SUCCESS); } catch (RemoteException e) { setDescription(_description + ", failed to release lock: " + e.getMessage()); } catch (SRMException e) { setDescription(_description + ", failed to finalise upload: " + "[" + e.getStatusCode() + "] " + e.getMessage()); } } @Nonnull private TPutRequestFileStatus getFileStatus(ArrayOfTPutRequestFileStatus container) throws SRMException { checkForFailure(container != null, "bad reply: null array-container of RequestFileStatus."); TPutRequestFileStatus[] statusArray = container.getStatusArray(); checkForFailure(statusArray != null, "bad reply: null RequestFileStatus array."); if (statusArray.length != 1) { throw new SRMException("bad reply: RequestFileStatus array with length " + statusArray.length + "."); } TPutRequestFileStatus status = statusArray[0]; checkForFailure(status != null, "bad reply: RequestFileStatus array with null element."); return status; } @Nonnull private TSURLReturnStatus getSURLStatus(ArrayOfTSURLReturnStatus container) throws SRMException { checkForFailure(container != null, "bad reply: null array-container of SURLReturnStatus."); TSURLReturnStatus[] statusArray = container.getStatusArray(); checkForFailure(statusArray != null, "bad reply: null SURLReturnStatus."); if (statusArray.length != 1) { throw new SRMException("bad reply: SURLReturnStatus array with length " + statusArray.length + "."); } TSURLReturnStatus status = statusArray[0]; checkForFailure(status != null, "bad reply: SURLReturnStatus array will null element"); return status; } } /** * Class for handling downloads specifically. */ public class GetTransfer extends AbstractSRMTransfer { public GetTransfer(URI source, File target) { super(source, target); } private void start() { _srmExecutor.execute(new FailOnBugTask(new Runnable(){ @Override public void run() { prepareToGet(); } })); } private void prepareToGet() { setDescription("requesting transfer URL."); // DPM requires TDirOption to have null value. ArrayOfTGetFileRequest fileRequests = new ArrayOfTGetFileRequest(new TGetFileRequest[]{ new TGetFileRequest(getSurl(), null), }); TTransferParameters params = new TTransferParameters(TAccessPattern.TRANSFER_MODE, TConnectionType.WAN, null, getProtocolPreference()); // FIXME: add user-description to allow rediscovery of ongoing requests SrmPrepareToGetRequest request = new SrmPrepareToGetRequest( null, fileRequests, null, null, null, (int)TimeUnit.DAYS.toSeconds(1), -1, null, null, params); try { SrmPrepareToGetResponse response = srm.srmPrepareToGet(request); setRequestToken(response.getRequestToken()); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_REQUEST_QUEUED, TStatusCode.SRM_REQUEST_INPROGRESS, TStatusCode.SRM_SUCCESS); TGetRequestFileStatus file = getFileStatus(response.getArrayOfFileStatuses()); TStatusCode requestStatusCode = response.getReturnStatus().getStatusCode(); if (requestStatusCode == TStatusCode.SRM_REQUEST_QUEUED || requestStatusCode == TStatusCode.SRM_REQUEST_INPROGRESS) { TStatusCode fileStatusCode = file.getStatus().getStatusCode(); if (fileStatusCode != requestStatusCode) { throw new SRMException("mismatch between file and response status: " + fileStatusCode + " != " + requestStatusCode); } setDescription("waiting to request status update."); Integer waitTime = file.getEstimatedWaitTime(); int delay = waitTime == null ? 1 : max(min(waitTime, 60), 1); _srmExecutor.schedule(new FailOnBugTask(new Runnable() { @Override public void run() { statusOfPrepareToGet(); } }), delay, TimeUnit.SECONDS); } else { checkSuccess(file.getStatus(), TStatusCode.SRM_FILE_PINNED); initiateTransfer(file.getTransferURL()); } } catch (RemoteException e) { fail(e); releaseFiles(); failed(e); } catch (SRMException e) { fail(e); releaseFiles(); failed(e); } } @Nonnull private TGetRequestFileStatus getFileStatus(ArrayOfTGetRequestFileStatus fileStatus) throws SRMException { checkForFailure(fileStatus != null, "bad reply: null array-container of RequestFileStatus."); TGetRequestFileStatus[] statuses = fileStatus.getStatusArray(); checkForFailure(statuses != null, "bad reply: null RequestFileStatus array."); if (statuses.length != 1) { throw new SRMException("bad reply: RequestFileStatus array with length " + statuses.length + "."); } TGetRequestFileStatus status = statuses[0]; checkForFailure(status != null, "bad reply: RequestFileStatus array with null element."); return status; } private void statusOfPrepareToGet() { setDescription("request status update."); SrmStatusOfGetRequestRequest request = new SrmStatusOfGetRequestRequest(getRequestToken(), null, new ArrayOfAnyURI(new URI[]{getSurl()})); try { SrmStatusOfGetRequestResponse response = srm.srmStatusOfGetRequest(request); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_REQUEST_QUEUED, TStatusCode.SRM_REQUEST_INPROGRESS, TStatusCode.SRM_SUCCESS); TStatusCode requestStatusCode = response.getReturnStatus().getStatusCode(); TGetRequestFileStatus file = getFileStatus(response.getArrayOfFileStatuses()); if (requestStatusCode == TStatusCode.SRM_REQUEST_QUEUED || requestStatusCode == TStatusCode.SRM_REQUEST_INPROGRESS) { TStatusCode fileStatusCode = file.getStatus().getStatusCode(); if (fileStatusCode != requestStatusCode) { throw new SRMException("mismatch between file and response status: " + fileStatusCode + " != " + requestStatusCode); } setDescription("waiting to request status update."); Integer waitTime = file.getEstimatedWaitTime(); int delay = waitTime == null ? 1 : Math.max(Math.min(waitTime, 60), 1); _srmExecutor.schedule(new FailOnBugTask(new Runnable() { @Override public void run() { statusOfPrepareToGet(); } }), delay, TimeUnit.SECONDS); } else { checkSuccess(file.getStatus(), TStatusCode.SRM_FILE_PINNED); initiateTransfer(file.getTransferURL()); } } catch (RemoteException e) { fail(e); releaseFiles(); failed(e); } catch (SRMException e) { fail(e); releaseFiles(); failed(e); } } private void initiateTransfer(URI turl) { FileTransfer transfer = agent.download(turl, getLocalFile()); if (transfer == null) { setDescription("unable to transfer from " + turl); releaseFiles(); } else { setDescription("preparing for transfer using " + turl); setTransfer(transfer); Futures.addCallback(transfer, new FutureCallback<Void>(){ @Override public void onSuccess(Void result) { setDescription("file transfer complete."); setTransfer(null); releaseFiles(); succeeded(); } @Override public void onFailure(Throwable t) { setDescription(t.getMessage()); setTransfer(null); releaseFiles(); failed(t); } }, _srmExecutor); } } private void releaseFiles() { if (getRequestToken() == null) { return; } String transferDescription = _description; String description = transferDescription; if (description.endsWith(".")) { description = description.substring(0, description.length()-1); } setDescription(description + ", finalising transfer"); SrmReleaseFilesRequest request = new SrmReleaseFilesRequest(getRequestToken(), null, new ArrayOfAnyURI(new URI[]{getSurl()}), false); try { SrmReleaseFilesResponse response = srm.srmReleaseFiles(request); checkSuccess(response.getReturnStatus(), TStatusCode.SRM_SUCCESS); setDescription(transferDescription); } catch (SRMException e) { setDescription(_description + " but failed to release lock: " + "[" + e.getStatusCode() + "] " + e.getMessage()); } catch (RemoteException e) { setDescription(_description + " but failed to release lock: " + e.getMessage()); } } } private void checkForFailure(boolean isOk, String message) throws SRMException { if (!isOk) { throw new SRMException(message); } } }