package org.dcache.srm.request; import com.google.common.collect.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.net.URI; import java.util.Date; import java.util.stream.Stream; import org.dcache.srm.SRMFileRequestNotFoundException; import org.dcache.srm.SRMInvalidRequestException; import org.dcache.srm.SRMTooManyResultsException; import org.dcache.srm.SRMUser; import org.dcache.srm.scheduler.IllegalStateTransition; import org.dcache.srm.scheduler.Scheduler; import org.dcache.srm.scheduler.State; import org.dcache.srm.util.RequestStatusTool; import org.dcache.srm.v2_2.ArrayOfTMetaDataPathDetail; import org.dcache.srm.v2_2.SrmLsResponse; import org.dcache.srm.v2_2.SrmStatusOfLsRequestResponse; import org.dcache.srm.v2_2.TMetaDataPathDetail; import org.dcache.srm.v2_2.TRequestType; import org.dcache.srm.v2_2.TReturnStatus; import org.dcache.srm.v2_2.TStatusCode; import static org.dcache.util.TimeUtils.relativeTimestamp; public final class LsRequest extends ContainerRequest<LsFileRequest> { private static final Logger logger = LoggerFactory.getLogger(LsRequest.class); private final long offset; // starting entry number private final long count; // max number of entries to be returned as set by client private final int maxNumOfResults; // max number of entries allowed by server, settable via configuration private int numberOfResults; // counts only entries allowed to be returned private long counter; // counts all entries private final int numOfLevels; // recursion level private final boolean longFormat; private String explanation; public LsRequest(SRMUser user, URI[] surls, long lifetime, long max_update_period, String client_host, long count, long offset, int numOfLevels, boolean longFormat, int maxNumOfResults ) { super(user, max_update_period, lifetime, "Ls request", client_host, id -> { ImmutableList.Builder<LsFileRequest> requests = ImmutableList.builder(); Stream.of(surls) .map(surl -> new LsFileRequest(id, surl, lifetime)) .forEachOrdered(requests::add); return requests.build(); }); this.count = count; this.offset = offset; this.numOfLevels = numOfLevels; this.longFormat = longFormat; this.maxNumOfResults = maxNumOfResults; } public LsRequest( long id, Long nextJobId, long creationTime, long lifetime, int stateId, SRMUser user, String scheduelerId, long schedulerTimeStamp, int numberOfRetries, long lastStateTransitionTime, JobHistory[] jobHistoryArray, ImmutableList<LsFileRequest> fileRequests, int retryDeltaTime, boolean should_updateretryDeltaTime, String description, String client_host, String statusCodeString, String explanation, boolean longFormat, int numOfLevels, long count, long offset) { super(id, nextJobId, creationTime, lifetime, stateId, user, scheduelerId, schedulerTimeStamp, numberOfRetries, lastStateTransitionTime, jobHistoryArray, fileRequests, retryDeltaTime, should_updateretryDeltaTime, description, client_host, statusCodeString); this.explanation=explanation; this.longFormat=longFormat; this.numOfLevels=numOfLevels; this.maxNumOfResults = 100; this.count=count; this.offset=offset; } @Nonnull @Override public LsFileRequest getFileRequestBySurl(URI surl) throws SRMFileRequestNotFoundException { for (LsFileRequest request : getFileRequests()) { if (request.getSurl().equals(surl)) { return request; } } throw new SRMFileRequestNotFoundException("ls file request for surl ="+surl +" is not found"); } @Override public Class<? extends Job> getSchedulerType() { return LsFileRequest.class; } @Override public void scheduleWith(Scheduler scheduler) throws InterruptedException, IllegalStateTransition { // save this request in request storage unconditionally // file requests will get stored as soon as they are // scheduled, and the saved state needs to be consistent saveJob(true); for (LsFileRequest request : getFileRequests()) { request.scheduleWith(scheduler); } } @Override public void onSrmRestart(Scheduler scheduler, boolean shouldFailJobs) { // Nothing to do. } public String kill() { return "request was ready, set all ready file statuses to done"; } @Override public void run() { } @Override protected void stateChanged(State oldState) { State state = getState(); if(state.isFinal()) { for (LsFileRequest fr : getFileRequests() ) { fr.wlock(); try { State fr_state = fr.getState(); if(!fr_state.isFinal()) { fr.setState(state, "Changing file state because request state has changed."); } } catch(IllegalStateTransition ist) { logger.error("Illegal State Transition : " +ist.getMessage()); } finally { fr.wunlock(); } } } } /** * Waits for up to timeout milliseconds for the request to * reach a non-queued state and then returns the current * SrmLsResponse for this LsRequest. */ public final SrmLsResponse getSrmLsResponse(long timeout) throws InterruptedException, SRMInvalidRequestException { /* To avoid a race condition between us querying the * current response and us waiting for a state change * notification, the notification scheme is counter * based. This guarantees that we do not loose any * notifications. A simple lock around the whole loop * would not have worked, as the call to * getSrmLsResponse may itself trigger a state change * and thus cause a deadlock when the state change is * signaled. */ Date deadline = getDateRelativeToNow(timeout); int counter = _stateChangeCounter.get(); SrmLsResponse response = getSrmLsResponse(); while (response.getReturnStatus().getStatusCode().isProcessing() && deadline.after(new Date()) && _stateChangeCounter.awaitChangeUntil(counter, deadline)) { counter = _stateChangeCounter.get(); response = getSrmLsResponse(); } return response; } public final SrmLsResponse getSrmLsResponse() throws SRMInvalidRequestException { SrmLsResponse response = new SrmLsResponse(); response.setReturnStatus(getTReturnStatus()); if (!response.getReturnStatus().getStatusCode().isProcessing()) { response.setDetails(new ArrayOfTMetaDataPathDetail(getPathDetailArray())); } else { response.setDetails(null); } response.setRequestToken(getTRequestToken()); return response; } public final SrmStatusOfLsRequestResponse getSrmStatusOfLsRequestResponse() throws SRMInvalidRequestException { return new SrmStatusOfLsRequestResponse( getTReturnStatus(), new ArrayOfTMetaDataPathDetail(getPathDetailArray())); } private String getTRequestToken() { return String.valueOf(getId()); } public TMetaDataPathDetail[] getPathDetailArray() throws SRMInvalidRequestException { int len = getFileRequests().size(); TMetaDataPathDetail detail[] = new TMetaDataPathDetail[len]; for(int i = 0; i<len; ++i) { LsFileRequest fr = getFileRequests().get(i); detail[i] = fr.getMetaDataPathDetail(); } return detail; } public final boolean increaseResultsNumAndContinue() throws SRMTooManyResultsException { wlock(); try { setNumberOfResults(getNumberOfResults() + 1); if(getNumberOfResults() > getMaxNumOfResults()) { StringBuilder sb = new StringBuilder(); sb.append("max results number of ").append(getMaxNumOfResults()); sb.append(" exceeded. Try to narrow down with count and use offset to get complete listing"); setExplanation(sb.toString()); setStatusCode(TStatusCode.SRM_TOO_MANY_RESULTS); throw new SRMTooManyResultsException(sb.toString()); } return getNumberOfResults() <= getCount() || getCount() == 0; } finally { wunlock(); } } @Override public TRequestType getRequestType() { return TRequestType.LS; } public long getCount(){ // final, no need to synchronize return count; } public long getOffset(){ // final, no need to synchronize return offset; } public int getNumOfLevels() { // final, no need to synchronize return numOfLevels; } public boolean getLongFormat() { return isLongFormat(); } public int getMaxNumOfResults() { rlock(); try { return maxNumOfResults; } finally { runlock(); } } public String getExplanation() { rlock(); try { return explanation; } finally { runlock(); } } public void setExplanation(String txt) { wlock(); try { this.explanation=txt; } finally { wunlock(); } } @Override public final TReturnStatus getTReturnStatus() { wlock(); try { updateStatus(); if(getStatusCode() != null) { return new TReturnStatus(getStatusCode(),getExplanation()); } int len = getNumOfFileRequest(); if (len == 0) { return new TReturnStatus(TStatusCode.SRM_INTERNAL_ERROR, "Could not find (deserialize) files in the request, NumOfFileRequest is 0"); } int failed_req = 0; int canceled_req = 0; int pending_req = 0; int running_req = 0; int done_req = 0; int got_exception = 0; int auth_failure = 0; for (LsFileRequest fr : getFileRequests()) { TReturnStatus fileReqRS = fr.getReturnStatus(); TStatusCode fileReqSC = fileReqRS.getStatusCode(); try { if (fileReqSC == TStatusCode.SRM_REQUEST_QUEUED) { pending_req++; } else if(fileReqSC == TStatusCode.SRM_REQUEST_INPROGRESS) { running_req++; } else if (fileReqSC == TStatusCode.SRM_ABORTED) { canceled_req++; } else if (fileReqSC == TStatusCode.SRM_AUTHORIZATION_FAILURE) { auth_failure++; } else if (RequestStatusTool.isFailedFileRequestStatus(fileReqRS)) { failed_req++; } else { done_req++; } } catch (Exception e) { logger.error(e.toString()); got_exception++; } } boolean isFinalState = getState().isFinal(); if (done_req == len ) { if (!isFinalState) { try { setState(State.DONE, "Operation completed."); } catch(IllegalStateTransition ist) { logger.error("Illegal State Transition : " +ist.getMessage()); } } return new TReturnStatus(TStatusCode.SRM_SUCCESS, null); } if (canceled_req == len ) { return new TReturnStatus(TStatusCode.SRM_ABORTED, "All ls file requests were cancelled"); } if ((pending_req==len)||(pending_req+running_req==len)||running_req==len) { return new TReturnStatus(TStatusCode.SRM_REQUEST_QUEUED, "All ls file requests are pending"); } if (auth_failure==len) { return new TReturnStatus(TStatusCode.SRM_AUTHORIZATION_FAILURE, "Client is not authorized to request information"); } if (got_exception==len) { return new TReturnStatus(TStatusCode.SRM_INTERNAL_ERROR, "SRM has an internal transient error, and client may try again"); } if (running_req > 0 || pending_req > 0) { return new TReturnStatus(TStatusCode.SRM_REQUEST_INPROGRESS, "Some files are completed, and some files are still on the queue. Details are on the files status"); } else { if (done_req>0) { if (!isFinalState) { try { setState(State.DONE,State.DONE.toString()); } catch(IllegalStateTransition ist) { logger.error("Illegal State Transition : " +ist.getMessage()); } } return new TReturnStatus(TStatusCode.SRM_PARTIAL_SUCCESS, "Some SURL requests successfully completed, and some SURL requests failed. Details are on the files status"); } else { if (!isFinalState) { try { setState(State.FAILED,State.FAILED.toString()); } catch(IllegalStateTransition ist) { logger.error("Illegal State Transition : " +ist.getMessage()); } } return new TReturnStatus(TStatusCode.SRM_FAILURE, "All ls requests failed in some way or another"); } } } finally { wunlock(); } } @Override public void toString(StringBuilder sb, boolean longformat) { sb.append(getNameForRequestType()).append(" id:").append(getId()); sb.append(" files:").append(getFileRequests().size()); sb.append(" state:").append(getState()); TStatusCode code = getStatusCode(); if (code != null) { sb.append(" status:").append(code); } sb.append(" by:").append(getUser().getDisplayName()); if (longformat) { sb.append('\n'); long now = System.currentTimeMillis(); sb.append(" Submitted: ").append(relativeTimestamp(getCreationTime(), now)).append('\n'); sb.append(" Expires: ").append(relativeTimestamp(getCreationTime() + getLifetime(), now)).append('\n'); sb.append(" Count : ").append(getCount()).append('\n'); sb.append(" Offset : ").append(getOffset()).append('\n'); sb.append(" LongFormat : ").append(getLongFormat()).append('\n'); sb.append(" NumOfLevels: ").append(getNumOfLevels()).append('\n'); sb.append(" History:\n"); sb.append(getHistory(" ")); for (LsFileRequest fr:getFileRequests()) { sb.append("\n"); fr.toString(sb, " ", true); } } } /** * @return the numberOfResults */ private int getNumberOfResults() { rlock(); try { return numberOfResults; } finally { runlock(); } } /** * @param numberOfResults the numberOfResults to set */ private void setNumberOfResults(int numberOfResults) { wlock(); try { this.numberOfResults = numberOfResults; } finally { wunlock(); } } /** * @return the counter */ public long getCounter() { rlock(); try { return counter; } finally { runlock(); } } /** * @return set the counter */ public void setCounter(long c) { wlock(); try { counter=c; } finally { wunlock(); } } public void incrementGlobalEntryCounter() { wlock(); try { setCounter(getCounter()+1); } finally { wunlock(); } } /** * @return check if we skip this record if the counter is less than offset */ public boolean shouldSkipThisRecord() { rlock(); try { return getCounter()<getOffset(); } finally { runlock(); } } /** * @return the longFormat */ private boolean isLongFormat() { // final, no need to synchronize return longFormat; } @Override public String getNameForRequestType() { return "Ls"; } }