/*
COPYRIGHT STATUS:
Dec 1st 2001, Fermi National Accelerator Laboratory (FNAL) documents and
software are sponsored by the U.S. Department of Energy under Contract No.
DE-AC02-76CH03000. Therefore, the U.S. Government retains a world-wide
non-exclusive, royalty-free license to publish or reproduce these documents
and software for U.S. Government purposes. All documents and software
available from this server are protected under the U.S. and Foreign
Copyright Laws, and FNAL reserves all rights.
Distribution of the software available from this server is free of
charge subject to the user following the terms of the Fermitools
Software Legal Information.
Redistribution and/or modification of the software shall be accompanied
by the Fermitools Software Legal Information (including the copyright
notice).
The user is asked to feed back problems, benefits, and/or suggestions
about the software to the Fermilab Software Providers.
Neither the name of Fermilab, the URA, nor the names of the contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
DISCLAIMER OF LIABILITY (BSD):
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FERMILAB,
OR THE URA, OR THE U.S. DEPARTMENT of ENERGY, OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Liabilities of the Government:
This software is provided by URA, independent from its Prime Contract
with the U.S. Department of Energy. URA is acting independently from
the Government and in its own private capacity and is not acting on
behalf of the U.S. Government, nor as its contractor nor its agent.
Correspondingly, it is understood and agreed that the U.S. Government
has no connection to this software and in no manner whatsoever shall
be liable for nor assume any responsibility or obligation for any claim,
cost, or damages arising out of or resulting from the use of the software
available from this server.
Export Control:
All documents and software available from this server are subject to U.S.
export control laws. Anyone downloading information from this server is
obligated to secure any necessary Government licenses before exporting
documents or software obtained from this server.
*/
/*
* ContainerRequest.java
*
* Created on July 5, 2002, 12:03 PM
*/
package org.dcache.srm.request;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.function.LongFunction;
import diskCacheV111.srm.RequestFileStatus;
import diskCacheV111.srm.RequestStatus;
import org.dcache.util.AtomicCounter;
import org.dcache.srm.SRMException;
import org.dcache.srm.SRMFileRequestNotFoundException;
import org.dcache.srm.SRMUser;
import org.dcache.srm.scheduler.IllegalStateTransition;
import org.dcache.srm.scheduler.State;
import org.dcache.srm.util.RequestStatusTool;
import org.dcache.srm.v2_2.TRequestSummary;
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.srm.handler.ReturnStatuses.*;
import static org.dcache.util.TimeUtils.relativeTimestamp;
/**
* This abstract class represents an "SRM request"
* We currently support "get","put", and "copy" requests
* which are the subclasses of this class
* Each Requests contains a set (array) of FileRequests
* each ContainerRequest is identified by its requestId
* and each FileRequest within ContainerRequest is identified by its fileRequestId
* The actual FileRequest arrays are in subclasses too.
*
* @author timur
* @version 1.0
*/
public abstract class ContainerRequest<R extends FileRequest<?>> extends Request {
private static final Logger logger = LoggerFactory.getLogger(ContainerRequest.class);
// dcache requires that once client created a connection to a dcache door,
// it uses the same door to make all following dcap transfers
// therefore we need to synchronize the recept of dcap turls
private URI previousTurl;
private final ImmutableList<R> fileRequests;
/**
* Counter used for notification between file requests and the
* parent request. The counter is incremented whenever when one of
* the file requests changes to one of a set of predefined states.
*/
protected final transient AtomicCounter _stateChangeCounter = new AtomicCounter();
/*
* public constructors
*/
/**
* Create a new request
* @param user
* srm user
* @param configuration
* srm configuration
*/
public ContainerRequest(SRMUser user,
long max_update_period,
long lifetime,
@Nullable String description,
String client_host,
LongFunction<ImmutableList<R>> factory)
{
super(user ,
max_update_period,
lifetime,
description,
client_host);
fileRequests = factory.apply(getId());
}
/**
* this constructor is used for restoring the previously
* saved ContainerRequest from persitance storage
*/
protected ContainerRequest(
long id,
Long nextJobId,
long creationTime,
long lifetime,
int stateId,
SRMUser user,
String scheduelerId,
long schedulerTimeStamp,
int numberOfRetries,
long lastStateTransitionTime,
JobHistory[] jobHistoryArray,
ImmutableList<R> fileRequests,
int retryDeltaTime,
boolean should_updateretryDeltaTime,
String description,
String client_host,
String statusCodeString) {
super( id,
nextJobId,
creationTime,
lifetime,
stateId,
user,
scheduelerId,
schedulerTimeStamp,
numberOfRetries,
lastStateTransitionTime,
jobHistoryArray,
retryDeltaTime,
should_updateretryDeltaTime,
description,
client_host,
statusCodeString);
this.fileRequests = fileRequests;
}
public final R getFileRequest(long fileRequestId){
for (R fileRequest: fileRequests) {
if (fileRequest.getId() == fileRequestId) {
return fileRequest;
}
}
return null;
}
/*
* public abstract instance methods
*/
/**
* gets a number of file requests in this request
* @return
* a number of file requests
*/
public int getNumOfFileRequest()
{
return fileRequests.size();
}
public URI getPreviousTurl() {
rlock();
try {
return previousTurl;
} finally {
runlock();
}
}
public void setPreviousTurl(URI s) {
wlock();
try {
previousTurl = s;
} finally {
wunlock();
}
}
@Override
public TReturnStatus abort(String reason)
{
boolean hasSuccess = false;
boolean hasFailure = false;
wlock();
try {
/* [ SRM 2.2, 5.11.2 ]
*
* a) srmAbortRequest terminates all files in the request regardless of the file
* state. Remove files from the queue, and release cached files if a limited
* lifetime is associated with the file.
* c) Abort must be allowed to all requests with requestToken.
* i) When duplicate abort request is issued on the same request, SRM_SUCCESS
* may be returned to all duplicate abort requests and no operations on
* duplicate abort requests are performed.
*/
updateStatus();
if (!getState().isFinal()) {
for (R file : getFileRequests()) {
try {
file.abort(reason);
hasSuccess = true;
} catch (SRMException | IllegalStateTransition e) {
hasFailure = true;
}
}
updateStatus();
}
} finally {
wunlock();
}
return getSummaryReturnStatus(hasFailure, hasSuccess);
}
public final void updateStatus() {
wlock();
try {
// we used to synchronize on this container request here, but
// it does not make sence as the file requests are being processed
// by multiple threads, and synchronizing here can cause a deadlock
// once all field access is made sycnhronized and file request need to
// access container request fields
// we can rely on the fact that
// once file request reach their final state, this state does not change
// so the combined logic
updateRetryDeltaTime();
boolean haveNonFinalRequests = false;
boolean haveFailedRequests = false;
boolean haveDoneRequests = false;
for (R fr : fileRequests) {
switch (fr.getState()) {
case DONE:
haveDoneRequests = true;
break;
case FAILED:
case CANCELED:
haveFailedRequests = true;
break;
default:
haveNonFinalRequests = true;
break;
}
}
if (!getState().isFinal() && !haveNonFinalRequests) {
stopUpdating();
if (haveFailedRequests) {
// no running, no ready and no pending requests
// there are only failed requests
// we can fail this request
// REVISIT: the above logic seems flawed: this branch is reached
// if there are multiple file requests, some in state
// Done and others in state Failed. The behaviour may
// be correct, but should be checked.
setState(State.FAILED, "File requests have failed.");
} else if (haveDoneRequests) {
// all requests are done
setState(State.DONE, "All file requests succeeded.");
} else {
// we should never be here, but we have this block
// in case request is restored with no files in it
logger.error("request state is unknown or no files in request!!!");
setState(State.FAILED, "Request state is unknown or no files in request!!!");
}
}
} catch (IllegalStateTransition e) {
logger.error("Illegal State Transition : " + e.getMessage());
} finally {
wunlock();
}
}
public TReturnStatus getTReturnStatus() {
updateStatus();
String description;
rlock();
try {
description = getLastJobChange().getDescription();
TStatusCode statusCode = getStatusCode();
if (statusCode != null) {
return new TReturnStatus(statusCode, description);
}
} finally {
runlock();
}
// we used to synchronize on this container request here, but
// it does not make sence as the file requests are being processed
// by multiple threads, and synchronizing here can cause a deadlock
// once all field access is made sycnhronized and file request need to
// access container request fields
// we can rely on the fact that
// once file request reach their final state, this state does not change
// so the combined logic
int failed_req = 0;
int failed_space_expired = 0;
int failed_no_free_space = 0;
int canceled_req = 0;
int pending_req = 0;
int running_req = 0;
int ready_req = 0;
int done_req = 0;
boolean failure = false;
for (R fr : fileRequests) {
TReturnStatus fileReqRS = fr.getReturnStatus();
TStatusCode fileReqSC = fileReqRS.getStatusCode();
if( fileReqSC == TStatusCode.SRM_REQUEST_QUEUED) {
pending_req++;
}
else if(fileReqSC == TStatusCode.SRM_REQUEST_INPROGRESS) {
running_req++;
}
else if(fileReqSC == TStatusCode.SRM_FILE_PINNED ||
fileReqSC == TStatusCode.SRM_SPACE_AVAILABLE) {
ready_req++;
}
else if(fileReqSC == TStatusCode.SRM_SUCCESS ||
fileReqSC == TStatusCode.SRM_RELEASED) {
done_req++;
}
else if(fileReqSC == TStatusCode.SRM_ABORTED) {
canceled_req++;
failure=true;
}
else if(fileReqSC == TStatusCode.SRM_NO_FREE_SPACE) {
failed_no_free_space++;
failure=true;
}
else if(fileReqSC == TStatusCode.SRM_SPACE_LIFETIME_EXPIRED) {
failed_space_expired++;
failure=true;
}
else if(RequestStatusTool.isFailedFileRequestStatus(fileReqRS)) {
failed_req++;
failure=true;
}
else {
logger.error("Unknown request status code {} for request {}", fr.getState(), fr.getId());
}
}
int len = getNumOfFileRequest();
if (canceled_req == len ) {
return new TReturnStatus(TStatusCode.SRM_ABORTED, description);
}
if (failed_req==len) {
return new TReturnStatus(TStatusCode.SRM_FAILURE, description);
}
if (ready_req==len || done_req==len || ready_req+done_req==len ) {
if (failure) {
return new TReturnStatus(TStatusCode.SRM_PARTIAL_SUCCESS, null);
}
else {
return new TReturnStatus(TStatusCode.SRM_SUCCESS, null);
}
}
if (pending_req==len) {
return new TReturnStatus(TStatusCode.SRM_REQUEST_QUEUED, description);
}
// SRM space is not enough to hold all requested SURLs for free. (so me thinks one fails - all fail)
if (failed_no_free_space>0) {
return new TReturnStatus(TStatusCode.SRM_NO_FREE_SPACE, description);
}
// space associated with the targetSpaceToken is expired. (so me thinks one fails - all fail)
if (failed_space_expired>0) {
return new TReturnStatus(TStatusCode.SRM_SPACE_LIFETIME_EXPIRED, description);
}
// we still have work to do:
if (running_req > 0 || pending_req > 0) {
return new TReturnStatus(TStatusCode.SRM_REQUEST_INPROGRESS, description);
}
else {
// all are done here
if (failure) {
if (ready_req > 0 || done_req > 0 ) {
//some succeeded some not
return new TReturnStatus(TStatusCode.SRM_PARTIAL_SUCCESS, null);
}
else {
//none succeeded
return new TReturnStatus(TStatusCode.SRM_FAILURE, description);
}
}
else {
//no single failure - we should not get to this piece if code
return new TReturnStatus(TStatusCode.SRM_SUCCESS, null);
}
}
}
public TRequestSummary getRequestSummary() {
TRequestSummary summary = new TRequestSummary();
summary.setStatus(getTReturnStatus());
summary.setRequestType(getRequestType());
summary.setRequestToken(String.valueOf(getId()));
int total_num = getNumOfFileRequest();
summary.setTotalNumFilesInRequest(total_num);
int num_of_failed=0;
int num_of_completed = 0;
int num_of_waiting = 0;
for(int i = 0; i< total_num; ++i) {
R fr;
rlock();
try {
fr = fileRequests.get(i);
} finally {
runlock();
}
TReturnStatus fileReqRS = fr.getReturnStatus();
TStatusCode fileReqSC = fileReqRS.getStatusCode();
if( fileReqSC == TStatusCode.SRM_REQUEST_QUEUED) {
num_of_waiting++;
} else if(fileReqSC == TStatusCode.SRM_SUCCESS ||
fileReqSC == TStatusCode.SRM_RELEASED) {
num_of_completed ++;
} else if (RequestStatusTool.isFailedFileRequestStatus(fileReqRS)) {
num_of_failed ++;
}
}
summary.setNumOfFailedFiles(num_of_failed);
summary.setNumOfCompletedFiles(num_of_completed);
summary.setNumOfWaitingFiles(num_of_waiting);
return summary;
}
public abstract TRequestType getRequestType();
/**
* check the object for the equality with this request
* <p>
* we return true only if object is this request
* @param o
* object to check for equality with
* @return
* result of the check
*/
@Override
public boolean equals(Object o) {
return o == this;
}
@Override
public int hashCode() {
return (int) (getId() ^ getId() >> 32);
}
/**
* Provide a simple human-friendly name for requests of this type.
*/
abstract String getNameForRequestType();
@Override
public void toString(StringBuilder sb, boolean longformat) {
sb.append(getNameForRequestType()).append(" id:").append(getId());
sb.append(" files:").append(fileRequests.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');
if (this instanceof DelegatedCredentialAware) {
Long id = ((DelegatedCredentialAware)this).getCredentialId();
RequestCredential credential = RequestCredential.getRequestCredential(id);
if (credential != null) {
sb.append(" Credential: ").append(credential.getCredentialName()).append('\n');
}
}
sb.append(" History:\n");
sb.append(getHistory(" "));
for (R fr:fileRequests) {
sb.append("\n");
fr.toString(sb, " ", longformat);
}
}
}
public void fileRequestStateChanged(R request)
{
switch (request.getState()) {
case RQUEUED:
case READY:
case DONE:
case CANCELED:
case FAILED:
_stateChangeCounter.increment();
}
}
@Nonnull
public abstract R getFileRequestBySurl(URI surl) throws SRMFileRequestNotFoundException;
public List<R> getFileRequests() {
return fileRequests;
}
@Override
public void tryToReady() {
fileRequests.forEach(FileRequest::tryToReady);
}
/**
* Constructs a Date object using the given milliseconds time
* value relative to the current point in time.
*
* Equivalent to 'new Date(System.currentTimeMillis() + delta)'
* except that underflows and overflows are taken into account.
*
* Used by subclasses.
*
* @param delta milliseconds relative to the current point in time
*/
protected Date getDateRelativeToNow(long delta)
{
long now = System.currentTimeMillis();
if (delta >= 0 && now >= Long.MAX_VALUE - delta) {
return new Date(Long.MAX_VALUE);
} else {
return new Date(now + delta);
}
}
}