/*
* dCache - http://www.dcache.org/
*
* Copyright (C) 2016 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 diskCacheV111.srm;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import eu.emi.security.authn.x509.X509Credential;
import org.apache.axis.types.URI;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.security.auth.Subject;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.PermissionDeniedCacheException;
import dmg.cells.nucleus.CellInfo;
import dmg.cells.nucleus.CellInfoProvider;
import dmg.cells.nucleus.CellPath;
import dmg.cells.nucleus.NoRouteToCellException;
import dmg.cells.zookeeper.PathChildrenCache;
import org.dcache.auth.LoginReply;
import org.dcache.auth.LoginStrategy;
import org.dcache.auth.Origin;
import org.dcache.cells.CellStub;
import org.dcache.cells.CuratorFrameworkAware;
import org.dcache.commons.stats.RequestCounters;
import org.dcache.commons.stats.RequestExecutionTimeGauges;
import org.dcache.commons.stats.rrd.RrdRequestCounters;
import org.dcache.commons.stats.rrd.RrdRequestExecutionTimeGauges;
import org.dcache.srm.SRMAuthenticationException;
import org.dcache.srm.SRMAuthorizationException;
import org.dcache.srm.SRMException;
import org.dcache.srm.SRMInternalErrorException;
import org.dcache.srm.SRMInvalidRequestException;
import org.dcache.srm.SrmRequest;
import org.dcache.srm.SrmResponse;
import org.dcache.srm.util.Axis;
import org.dcache.srm.util.JDC;
import org.dcache.srm.v2_2.ArrayOfAnyURI;
import org.dcache.srm.v2_2.ArrayOfString;
import org.dcache.srm.v2_2.ArrayOfTBringOnlineRequestFileStatus;
import org.dcache.srm.v2_2.ArrayOfTCopyFileRequest;
import org.dcache.srm.v2_2.ArrayOfTCopyRequestFileStatus;
import org.dcache.srm.v2_2.ArrayOfTExtraInfo;
import org.dcache.srm.v2_2.ArrayOfTGetFileRequest;
import org.dcache.srm.v2_2.ArrayOfTGetRequestFileStatus;
import org.dcache.srm.v2_2.ArrayOfTMetaDataPathDetail;
import org.dcache.srm.v2_2.ArrayOfTPutFileRequest;
import org.dcache.srm.v2_2.ArrayOfTPutRequestFileStatus;
import org.dcache.srm.v2_2.ArrayOfTRequestSummary;
import org.dcache.srm.v2_2.ArrayOfTRequestTokenReturn;
import org.dcache.srm.v2_2.ArrayOfTSURLReturnStatus;
import org.dcache.srm.v2_2.SrmAbortFilesRequest;
import org.dcache.srm.v2_2.SrmAbortFilesResponse;
import org.dcache.srm.v2_2.SrmAbortRequestRequest;
import org.dcache.srm.v2_2.SrmAbortRequestResponse;
import org.dcache.srm.v2_2.SrmBringOnlineRequest;
import org.dcache.srm.v2_2.SrmBringOnlineResponse;
import org.dcache.srm.v2_2.SrmChangeSpaceForFilesRequest;
import org.dcache.srm.v2_2.SrmChangeSpaceForFilesResponse;
import org.dcache.srm.v2_2.SrmCheckPermissionRequest;
import org.dcache.srm.v2_2.SrmCheckPermissionResponse;
import org.dcache.srm.v2_2.SrmCopyRequest;
import org.dcache.srm.v2_2.SrmCopyResponse;
import org.dcache.srm.v2_2.SrmExtendFileLifeTimeInSpaceRequest;
import org.dcache.srm.v2_2.SrmExtendFileLifeTimeInSpaceResponse;
import org.dcache.srm.v2_2.SrmExtendFileLifeTimeRequest;
import org.dcache.srm.v2_2.SrmExtendFileLifeTimeResponse;
import org.dcache.srm.v2_2.SrmGetPermissionRequest;
import org.dcache.srm.v2_2.SrmGetPermissionResponse;
import org.dcache.srm.v2_2.SrmGetRequestSummaryRequest;
import org.dcache.srm.v2_2.SrmGetRequestSummaryResponse;
import org.dcache.srm.v2_2.SrmGetRequestTokensRequest;
import org.dcache.srm.v2_2.SrmGetRequestTokensResponse;
import org.dcache.srm.v2_2.SrmGetSpaceMetaDataRequest;
import org.dcache.srm.v2_2.SrmGetSpaceMetaDataResponse;
import org.dcache.srm.v2_2.SrmGetSpaceTokensRequest;
import org.dcache.srm.v2_2.SrmGetSpaceTokensResponse;
import org.dcache.srm.v2_2.SrmGetTransferProtocolsRequest;
import org.dcache.srm.v2_2.SrmGetTransferProtocolsResponse;
import org.dcache.srm.v2_2.SrmLsRequest;
import org.dcache.srm.v2_2.SrmLsResponse;
import org.dcache.srm.v2_2.SrmMkdirRequest;
import org.dcache.srm.v2_2.SrmMkdirResponse;
import org.dcache.srm.v2_2.SrmMvRequest;
import org.dcache.srm.v2_2.SrmMvResponse;
import org.dcache.srm.v2_2.SrmPingRequest;
import org.dcache.srm.v2_2.SrmPingResponse;
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.SrmPurgeFromSpaceRequest;
import org.dcache.srm.v2_2.SrmPurgeFromSpaceResponse;
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.SrmReleaseSpaceRequest;
import org.dcache.srm.v2_2.SrmReleaseSpaceResponse;
import org.dcache.srm.v2_2.SrmReserveSpaceRequest;
import org.dcache.srm.v2_2.SrmReserveSpaceResponse;
import org.dcache.srm.v2_2.SrmResumeRequestRequest;
import org.dcache.srm.v2_2.SrmResumeRequestResponse;
import org.dcache.srm.v2_2.SrmRmRequest;
import org.dcache.srm.v2_2.SrmRmResponse;
import org.dcache.srm.v2_2.SrmRmdirRequest;
import org.dcache.srm.v2_2.SrmRmdirResponse;
import org.dcache.srm.v2_2.SrmSetPermissionRequest;
import org.dcache.srm.v2_2.SrmSetPermissionResponse;
import org.dcache.srm.v2_2.SrmStatusOfBringOnlineRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfBringOnlineRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfChangeSpaceForFilesRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfChangeSpaceForFilesRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfCopyRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfCopyRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfGetRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfGetRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfLsRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfLsRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfPutRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfPutRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfReserveSpaceRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfReserveSpaceRequestResponse;
import org.dcache.srm.v2_2.SrmStatusOfUpdateSpaceRequestRequest;
import org.dcache.srm.v2_2.SrmStatusOfUpdateSpaceRequestResponse;
import org.dcache.srm.v2_2.SrmSuspendRequestRequest;
import org.dcache.srm.v2_2.SrmSuspendRequestResponse;
import org.dcache.srm.v2_2.SrmUpdateSpaceRequest;
import org.dcache.srm.v2_2.SrmUpdateSpaceResponse;
import org.dcache.srm.v2_2.TBringOnlineRequestFileStatus;
import org.dcache.srm.v2_2.TCopyFileRequest;
import org.dcache.srm.v2_2.TCopyRequestFileStatus;
import org.dcache.srm.v2_2.TExtraInfo;
import org.dcache.srm.v2_2.TGetFileRequest;
import org.dcache.srm.v2_2.TGetRequestFileStatus;
import org.dcache.srm.v2_2.TMetaDataPathDetail;
import org.dcache.srm.v2_2.TPutFileRequest;
import org.dcache.srm.v2_2.TPutRequestFileStatus;
import org.dcache.srm.v2_2.TRequestSummary;
import org.dcache.srm.v2_2.TRequestTokenReturn;
import org.dcache.srm.v2_2.TReturnStatus;
import org.dcache.srm.v2_2.TSURLReturnStatus;
import org.dcache.srm.v2_2.TStatusCode;
import org.dcache.util.CertificateFactories;
import org.dcache.util.NetLoggerBuilder;
import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.*;
import static org.dcache.srm.handler.ReturnStatuses.getSummaryReturnStatus;
import static org.dcache.srm.v2_2.TStatusCode.*;
/**
* Utility class to submit requests to the SRM backend service.
*/
public class SrmHandler implements CellInfoProvider, CuratorFrameworkAware
{
private static final Logger LOGGER = LoggerFactory.getLogger(SrmHandler.class);
private static final Set<TStatusCode> FAILURES =
ImmutableSet.of(SRM_FAILURE,
SRM_AUTHENTICATION_FAILURE, SRM_AUTHORIZATION_FAILURE, SRM_INVALID_REQUEST, SRM_INVALID_PATH,
SRM_FILE_LIFETIME_EXPIRED, SRM_SPACE_LIFETIME_EXPIRED, SRM_EXCEED_ALLOCATION, SRM_NO_USER_SPACE,
SRM_NO_FREE_SPACE, SRM_DUPLICATION_ERROR, SRM_NON_EMPTY_DIRECTORY, SRM_TOO_MANY_RESULTS,
SRM_INTERNAL_ERROR, SRM_FATAL_INTERNAL_ERROR, SRM_NOT_SUPPORTED, SRM_ABORTED,
SRM_REQUEST_TIMED_OUT, SRM_FILE_BUSY, SRM_FILE_LOST, SRM_FILE_UNAVAILABLE, SRM_CUSTOM_STATUS);
private final RequestLogger[] loggers =
{ new RequestExecutionTimeGaugeLogger(), new CounterLogger(), new AccessLogger() };
private final RequestCounters<Class<?>> srmServerCounters = new RequestCounters<>("srmv2");
private final RequestExecutionTimeGauges<Class<?>> srmServerGauges = new RequestExecutionTimeGauges<>("srmv2");
private final CertificateFactory cf = CertificateFactories.newX509CertificateFactory();
private final LoadingCache<Class, Optional<Field>> requestTokenFieldCache = CacheBuilder.newBuilder()
.build(new CacheLoader<Class, Optional<Field>>()
{
@Override
public Optional<Field> load(Class clazz)
{
try {
Field field = clazz.getDeclaredField("requestToken");
field.setAccessible(true);
return Optional.of(field);
} catch (NoSuchFieldException e) {
return Optional.empty();
}
}
});
private PathChildrenCache backends;
private String counterRrdDirectory;
private String gaugeRrdDirectory;
private boolean isClientDNSLookup;
private LoginStrategy loginStrategy;
private CellStub srmManagerStub;
private ArrayOfTExtraInfo pingExtraInfo;
private CuratorFramework client;
@Override
public void setCuratorFramework(CuratorFramework client)
{
this.client = client;
}
public void setCounterRrdDirectory(String counterRrdDirectory)
{
this.counterRrdDirectory = counterRrdDirectory;
}
public void setGaugeRrdDirectory(String gaugeRrdDirectory)
{
this.gaugeRrdDirectory = gaugeRrdDirectory;
}
public void setClientDNSLookup(boolean clientDNSLookup)
{
isClientDNSLookup = clientDNSLookup;
}
@Required
public void setSrmManagerStub(CellStub srmManagerStub)
{
this.srmManagerStub = srmManagerStub;
}
@Required
public void setLoginStrategy(LoginStrategy loginStrategy)
{
this.loginStrategy = loginStrategy;
}
@Required
public void setPingExtraInfo(ImmutableMap<String, String> pingExtraInfo)
{
this.pingExtraInfo = buildExtraInfo(pingExtraInfo);
}
@PostConstruct
public void init() throws Exception
{
if (!Strings.isNullOrEmpty(counterRrdDirectory)) {
String rrddir = counterRrdDirectory + File.separatorChar + "srmv2";
RrdRequestCounters<?> rrdSrmServerCounters =
new RrdRequestCounters<>(srmServerCounters, rrddir);
rrdSrmServerCounters.startRrdUpdates();
rrdSrmServerCounters.startRrdGraphPlots();
}
if (!Strings.isNullOrEmpty(gaugeRrdDirectory)) {
File rrddir = new File(gaugeRrdDirectory + File.separatorChar + "srmv2");
RrdRequestExecutionTimeGauges<?> rrdSrmServerGauges =
new RrdRequestExecutionTimeGauges<>(srmServerGauges, rrddir);
rrdSrmServerGauges.startRrdUpdates();
rrdSrmServerGauges.startRrdGraphPlots();
}
backends = new PathChildrenCache(client, "/dcache/srm/backends", true);
backends.start();
}
@PreDestroy
public void shutdown() throws IOException
{
if (backends != null) {
backends.close();
}
}
@Override
public void getInfo(PrintWriter pw)
{
pw.println(srmServerCounters);
pw.println(srmServerGauges);
}
@Override
public CellInfo getCellInfo(CellInfo info)
{
return info;
}
private ArrayOfTExtraInfo buildExtraInfo(Map<String,String> items)
{
if (items.isEmpty()) {
return null;
}
TExtraInfo[] extraInfo = new TExtraInfo[items.size()];
int i = 0;
for (Map.Entry<String,String> item : items.entrySet()) {
extraInfo [i++] = new TExtraInfo(item.getKey(), Strings.emptyToNull(item.getValue()));
}
return new ArrayOfTExtraInfo(extraInfo);
}
public Object handleRequest(String requestName, Object request) throws RemoteException
{
long startTimeStamp = System.currentTimeMillis();
// requestName values all start "srm". This is redundant, so may
// be removed when creating the session id. The initial character is
// converted to lowercase, so "srmPrepareToPut" becomes "prepareToPut".
String session = "srm2:" +
Character.toLowerCase(requestName.charAt(3)) +
requestName.substring(4);
try (JDC ignored = JDC.createSession(session)) {
for (RequestLogger logger : loggers) {
logger.request(requestName, request);
}
Subject user = null;
Object response;
if (requestName.equals("srmPing")) {
// Ping is special as it isn't authenticated and unable to return a failure
response = new SrmPingResponse("v2.2", pingExtraInfo);
} else {
try {
LoginReply login = login();
user = login.getSubject();
X509Credential credential = Axis.getDelegatedCredential().orElse(null);
String remoteIP = Axis.getRemoteAddress();
String remoteHost = isClientDNSLookup ?
InetAddresses.forString(remoteIP).getCanonicalHostName() : remoteIP;
response = dispatch(login, credential, remoteHost, requestName, request);
} catch (SRMInternalErrorException e) {
LOGGER.error(e.getMessage());
response = getFailedResponse(requestName, e.getStatusCode(),
"Authentication failed (server log contains additional information).");
} catch (SRMAuthorizationException e) {
LOGGER.info(e.getMessage());
response = getFailedResponse(requestName, e.getStatusCode(), "Permission denied.");
} catch (SRMAuthenticationException e) {
LOGGER.warn(e.getMessage());
response = getFailedResponse(requestName, e.getStatusCode(),
"Authentication failed (server log contains additional information).");
} catch (SRMException e) {
response = getFailedResponse(requestName, e.getStatusCode(), e.getMessage());
} catch (PermissionDeniedCacheException e) {
response = getFailedResponse(requestName, TStatusCode.SRM_AUTHORIZATION_FAILURE, e.getMessage());
} catch (CacheException e) {
response = getFailedResponse(requestName, TStatusCode.SRM_INTERNAL_ERROR, e.getMessage());
} catch (InterruptedException e) {
response = getFailedResponse(requestName, TStatusCode.SRM_FATAL_INTERNAL_ERROR, "Server shutdown.");
} catch (NoRouteToCellException e) {
LOGGER.error(e.getMessage());
response = getFailedResponse(requestName, TStatusCode.SRM_INTERNAL_ERROR,
"SRM backend serving this request is currently offline.");
}
}
long time = System.currentTimeMillis() - startTimeStamp;
for (RequestLogger logger : loggers) {
logger.response(requestName, request, response, user, time);
}
return response;
}
}
private LoginReply login() throws SRMAuthenticationException, CacheException, SRMInternalErrorException
{
try {
Subject subject = new Subject();
X509Certificate[] chain = Axis.getCertificateChain().orElseThrow(
() -> new SRMAuthenticationException("Client's certificate chain is missing from request"));
subject.getPublicCredentials().add(cf.generateCertPath(asList(chain)));
subject.getPrincipals().add(new Origin(InetAddresses.forString(Axis.getRemoteAddress())));
return loginStrategy.login(subject);
} catch (CertificateException e) {
throw new SRMInternalErrorException("Failed to process certificate chain.", e);
}
}
private Object dispatch(LoginReply login, X509Credential credential, String remoteHost,
String requestName, Object request)
throws CacheException, InterruptedException, SRMException, NoRouteToCellException
{
Function<Object,SrmRequest> toMessage =
req -> new SrmRequest(login.getSubject(), login.getLoginAttributes(), credential,
remoteHost, requestName, req);
try {
switch (requestName) {
case "srmGetRequestTokens":
return dispatch((SrmGetRequestTokensRequest) request, toMessage);
case "srmGetRequestSummary":
return dispatch((SrmGetRequestSummaryRequest) request, toMessage);
case "srmReleaseFiles":
return dispatch((SrmReleaseFilesRequest) request, toMessage);
case "srmExtendFileLifeTime":
// The token in extend file life time is optional, however since we do
// not support this request anyway, there is no harm in not doing any
// special processing.
return dispatch(request, toMessage);
default:
return dispatch(request, toMessage);
}
} catch (ExecutionException e) {
Throwables.propagateIfInstanceOf(e.getCause(), SRMException.class);
Throwables.propagateIfInstanceOf(e.getCause(), CacheException.class);
Throwables.propagateIfInstanceOf(e.getCause(), NoRouteToCellException.class);
throw Throwables.propagate(e.getCause());
}
}
private Object dispatch(SrmGetRequestTokensRequest request, Function<Object, SrmRequest> toMessage)
throws InterruptedException, ExecutionException
{
List<ListenableFuture<SrmResponse>> futures =
backends.getCurrentData().stream()
.map(this::toCellPath)
.map(path -> srmManagerStub.send(path, toMessage.apply(request), SrmResponse.class))
.collect(toList());
return mapGetRequestTokensResponse(Futures.allAsList(futures).get());
}
private Object dispatch(SrmGetRequestSummaryRequest summaryRequest, Function<Object, SrmRequest> toMessage)
throws SRMInvalidRequestException, InterruptedException, CacheException, NoRouteToCellException
{
String[] requestTokens = summaryRequest.getArrayOfRequestTokens().getStringArray();
if (requestTokens == null || requestTokens.length == 0) {
throw new SRMInvalidRequestException("arrayOfRequestTokens is empty");
}
Map<String, ListenableFuture<TRequestSummary>> futureMap =
provideRequestSummary(toMessage, summaryRequest.getAuthorizationID(), requestTokens);
return toGetRequestSummaryResponse(futureMap);
}
private Object dispatch(SrmReleaseFilesRequest request, Function<Object, SrmRequest> toMessage)
throws InterruptedException, ExecutionException, SRMInternalErrorException
{
if (request.getRequestToken() == null) {
// TODO: We could do the unpin calls here to avoid that each backend does that repeatedly
List<ListenableFuture<SrmResponse>> futures =
backends.getCurrentData().stream()
.map(this::toCellPath)
.map(path -> srmManagerStub.send(path, toMessage.apply(request), SrmResponse.class))
.collect(toList());
return mapReleaseFilesResponse(request, Futures.allAsList(futures).get());
} else {
return dispatch((Object) request, toMessage);
}
}
private Object dispatch(Object request, Function<Object, SrmRequest> toMessage)
throws InterruptedException, ExecutionException, SRMInternalErrorException
{
try (MappedRequest mapped = mapRequest(request)) {
ListenableFuture<SrmResponse> future =
(mapped == null)
? srmManagerStub.send(toMessage.apply(request), SrmResponse.class)
: srmManagerStub.send(mapped.getBackend(), toMessage.apply(mapped.getRequest()), SrmResponse.class);
return mapResponse(future.get());
}
}
private SrmGetRequestSummaryResponse toGetRequestSummaryResponse(
Map<String, ListenableFuture<TRequestSummary>> futureMap)
throws InterruptedException, CacheException, NoRouteToCellException
{
boolean hasFailure = false;
boolean hasSuccess = false;
List<TRequestSummary> summaries = new ArrayList<>();
for (Map.Entry<String, ListenableFuture<TRequestSummary>> entry : futureMap.entrySet()) {
try {
summaries.add(entry.getValue().get());
hasSuccess = true;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof SRMException) {
summaries.add(createRequestSummaryFailure(
entry.getKey(), ((SRMException) cause).getStatusCode(), cause.getMessage()));
hasFailure = true;
} else {
Throwables.propagateIfInstanceOf(cause, CacheException.class);
Throwables.propagateIfInstanceOf(cause, NoRouteToCellException.class);
throw Throwables.propagate(cause);
}
}
}
TReturnStatus status;
if (!hasFailure) {
status = new TReturnStatus(SRM_SUCCESS, "All request statuses have been retrieved.");
} else if (hasSuccess) {
status = new TReturnStatus(SRM_PARTIAL_SUCCESS, "Some request statuses have been retrieved.");
} else {
status = new TReturnStatus(SRM_FAILURE, "No request statuses have been retrieved.");
}
return new SrmGetRequestSummaryResponse(
status, new ArrayOfTRequestSummary(summaries.stream().toArray(TRequestSummary[]::new)));
}
/**
* Provide request summaries for a collection of request tokens. The result is
* provided as a map from request token to a future request summary as the result is
* typically fetched asynchronously from the backends.
*/
private Map<String, ListenableFuture<TRequestSummary>> provideRequestSummary(
Function<Object,SrmRequest> toMessage, String authorizationId, String[] tokens)
{
Function<String, Optional<CellPath>> optionalBackendOf =
token -> Optional.ofNullable(hasPrefix(token) ? backendOf(token) : null);
Map<Optional<CellPath>, Set<String>> tokensByBackend =
Stream.of(tokens).collect(groupingBy(optionalBackendOf, toSet()));
return tokensByBackend.entrySet().stream()
.map(e -> e.getKey().isPresent()
? provideRequestSummaryForTokenWithBackend(toMessage, authorizationId, e.getValue(), e.getKey().get())
: provideRequestSummaryForTokenWithoutBackend(e.getValue()))
.flatMap(m -> m.entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* Returns a map of responses for a list of tokens that cannot be mapped to a backend.
*/
private Map<String, ? extends ListenableFuture<TRequestSummary>> provideRequestSummaryForTokenWithoutBackend(
Collection<String> tokens)
{
return tokens.stream().collect(toMap(Function.identity(), this::provideRequestSummaryForTokenWithoutBackend));
}
/**
* Returns a response for a token that cannot be mapped to a backend. The response depends
* on whether the token has a valid backend id prefix (backend if offline) or not (token is invalid).
*/
private ListenableFuture<TRequestSummary> provideRequestSummaryForTokenWithoutBackend(String token)
{
if (!hasPrefix(token)) {
return Futures.immediateFailedFuture(new SRMInvalidRequestException("No such request token: " + token));
}
return Futures.immediateFailedFuture(
new SRMInvalidRequestException("Backend for request " + token + " is currently unavailable."));
}
/**
* Returns a map of summaries from backends for a list of tokens.
*/
private Map<String, ? extends ListenableFuture<TRequestSummary>> provideRequestSummaryForTokenWithBackend(
Function<Object, SrmRequest> toMessage, String authorizationId, Collection<String> tokens, CellPath backend)
{
SrmRequest msg = toMessage.apply(createRequestSummaryRequest(authorizationId, tokens));
ListenableFuture<SrmResponse> futureResponse = srmManagerStub.send(backend, msg, SrmResponse.class);
return transformToMap(tokens, futureResponse, r -> toRequestSummaries(tokens, r), this::translateBackendError);
}
/**
* Injects a summary into a settable future, translating errors that indicate failure to obtain
* a summary to a failure future.
*/
private void translateBackendError(TRequestSummary summary, SettableFuture<TRequestSummary> future)
{
TStatusCode statusCode = summary.getStatus().getStatusCode();
if (statusCode == SRM_INVALID_REQUEST) {
future.setException(new SRMInvalidRequestException(
"No such request token: " + summary.getRequestToken()));
} else if (statusCode == SRM_FAILURE && summary.getRequestType() == null) {
future.setException(new SRMException(summary.getStatus().getExplanation()));
} else {
future.set(summary);
}
}
/**
* Returns a backend request summary request for a list of tokens.
*/
private SrmGetRequestSummaryRequest createRequestSummaryRequest(String authorizationId, Collection<String> tokens)
{
String[] backendTokens = tokens.stream().map(SrmHandler::backendTokenOf).toArray(String[]::new);
return new SrmGetRequestSummaryRequest(new ArrayOfString(backendTokens), authorizationId);
}
/**
* Returns a map of request summaries for a backend response.
*/
private Map<String, TRequestSummary> toRequestSummaries(Collection<String> tokens, SrmResponse response)
{
TRequestSummary[] summaries = ((SrmGetRequestSummaryResponse) response.getResponse()).getArrayOfRequestSummaries().getSummaryArray();
Map<String, TRequestSummary> summaryByBackendToken =
Stream.of(summaries).collect(toMap(TRequestSummary::getRequestToken, Function.identity()));
return tokens.stream()
.collect(toMap(token -> token,
token -> mapRequestSummary(token, summaryByBackendToken.get(backendTokenOf(token)))));
}
/**
* Map a backend request summary to a frontend request summary.
*/
private TRequestSummary mapRequestSummary(String token, TRequestSummary summary)
{
return (summary == null)
? createRequestSummaryFailure(token, SRM_INVALID_REQUEST, "No such request token: " + token)
: new TRequestSummary(token, summary.getStatus(), summary.getRequestType(),
summary.getTotalNumFilesInRequest(), summary.getNumOfCompletedFiles(),
summary.getNumOfWaitingFiles(), summary.getNumOfFailedFiles());
}
private static TRequestSummary createRequestSummaryFailure(String token, TStatusCode status, String explanation)
{
return new TRequestSummary(token, new TReturnStatus(status, explanation),
null, null, null, null, null);
}
/**
* Transforms a future to a map of future values.
*
* The return map contains {@code keys} elements. Each key is mapped to a future value. If
* {@code future} fails, all returned futures fail with the same error. Otherwise {@code mapper}
* maps the return value of {@code future} to a map of values. {@code acceptor} is called for
* each value, applying the value to the settable future in the returned map.
*
* @param keys The keys of the map to return.
* @param future The future providing the input value.
* @param mapper A function that maps the future input to a map of output values.
* @param applicator Consumer that applies an output value to a future output value.
*/
private static <K, T, V> Map<K, ? extends ListenableFuture<V>> transformToMap(
Collection<K> keys, ListenableFuture<T> future,
Function<T, Map<K, V>> mapper,
BiConsumer<V, SettableFuture<V>> applicator)
{
Map<K, SettableFuture<V>> result = keys.stream().collect(toMap(key -> key, key -> SettableFuture.create()));
Futures.addCallback(future, new FutureCallback<T>()
{
@Override
public void onSuccess(T t)
{
Map<K, V> map = mapper.apply(t);
result.forEach((key, f) -> applicator.accept(map.get(key), f));
}
@Override
public void onFailure(Throwable t)
{
result.values().forEach(f -> f.setException(t));
}
});
return result;
}
private CellPath toCellPath(ChildData data)
{
return new CellPath(new String(data.getData(), US_ASCII));
}
/**
* Encapsulates the result of mapping a request to a backend.
*
* The request is modified inline to reflect the internal request token. This
* avoids copying the request, however the modification must be undone by
* closing this MappedRequest once the mapped request is no longer needed.
*/
private class MappedRequest implements AutoCloseable
{
private final Object request;
private final CellPath backend;
private final Field field;
private final String token;
MappedRequest(Object request, CellPath backend, Field field, String token) throws IllegalAccessException
{
this.request = request;
this.backend = backend;
this.field = field;
this.token = token;
field.set(request, backendTokenOf(token));
}
CellPath getBackend()
{
return backend;
}
Object getRequest()
{
return request;
}
@Override
public void close()
{
try {
field.set(request, token);
} catch (IllegalAccessException e) {
Throwables.propagate(e);
}
}
}
private MappedRequest mapRequest(Object request) throws SRMInternalErrorException
{
Optional<Field> field = requestTokenFieldCache.getUnchecked(request.getClass());
if (field.isPresent()) {
try {
Field f = field.get();
String token = (String) f.get(request);
if (hasPrefix(token)) {
CellPath path = backendOf(token);
if (path == null) {
throw new SRMInternalErrorException("SRM backend serving this request token is currently offline.");
}
return new MappedRequest(request, path, f, token);
}
} catch (IllegalAccessException e) {
Throwables.propagate(e);
}
}
return null;
}
private SrmGetRequestTokensResponse mapGetRequestTokensResponse(List<SrmResponse> responses)
{
List<TRequestTokenReturn> tokens = new ArrayList<>();
for (SrmResponse srmResponse : responses) {
SrmGetRequestTokensResponse response =
(SrmGetRequestTokensResponse) srmResponse.getResponse();
if (response.getReturnStatus().getStatusCode() != SRM_SUCCESS) {
return response;
}
for (TRequestTokenReturn token : response.getArrayOfRequestTokens().getTokenArray()) {
tokens.add(new TRequestTokenReturn(prefix(srmResponse.getId(), token.getRequestToken()),
token.getCreatedAtTime()));
}
}
ArrayOfTRequestTokenReturn arrayOfRequestTokens = new ArrayOfTRequestTokenReturn(
tokens.stream().toArray(TRequestTokenReturn[]::new));
return new SrmGetRequestTokensResponse(new TReturnStatus(SRM_SUCCESS, "Request processed successfully."), arrayOfRequestTokens);
}
private SrmReleaseFilesResponse mapReleaseFilesResponse(SrmReleaseFilesRequest request, List<SrmResponse> responses)
{
Map<URI,TSURLReturnStatus> map = new HashMap<>();
for (SrmResponse srmResponse : responses) {
SrmReleaseFilesResponse response = (SrmReleaseFilesResponse) srmResponse.getResponse();
for (TSURLReturnStatus status : response.getArrayOfFileStatuses().getStatusArray()) {
if (status.getStatus().getStatusCode() == SRM_SUCCESS) {
map.put(status.getSurl(), status);
} else if (status.getStatus().getStatusCode() == SRM_INVALID_PATH) {
// no entry
} else if (status.getStatus().getStatusCode() == SRM_AUTHORIZATION_FAILURE) {
map.putIfAbsent(status.getSurl(), status);
} else if (status.getStatus().getStatusCode() == SRM_FILE_LIFETIME_EXPIRED) {
map.putIfAbsent(status.getSurl(), status);
} else if (status.getStatus().getStatusCode() == SRM_FAILURE) {
map.putIfAbsent(status.getSurl(), status);
}
}
}
TSURLReturnStatus[] statuses = Stream.of(request.getArrayOfSURLs().getUrlArray())
.map(surl -> {
TSURLReturnStatus status = map.get(surl);
return (status != null) ? status : new TSURLReturnStatus(surl, new TReturnStatus(SRM_INVALID_PATH,
"File not found"));
})
.toArray(TSURLReturnStatus[]::new);
return new SrmReleaseFilesResponse(getSummaryReturnStatus(statuses), new ArrayOfTSURLReturnStatus(statuses));
}
private Object mapResponse(SrmResponse response)
{
Object o = response.getResponse();
Optional<Field> field = requestTokenFieldCache.getUnchecked(o.getClass());
field.ifPresent(f -> {
try {
f.set(o, prefix(response.getId(), (String) f.get(o)));
} catch (IllegalAccessException e) {
Throwables.propagate(e);
}
});
return o;
}
private CellPath backendOf(String prefixedToken)
{
checkArgument(hasPrefix(prefixedToken));
String path = SrmService.getZooKeeperBackendPath(backendIdOf(prefixedToken));
ChildData data = backends.getCurrentData(path);
if (data != null) {
return toCellPath(data);
}
return null;
}
private static String backendIdOf(String prefixedToken)
{
return prefixedToken.substring(0, 8);
}
private static String backendTokenOf(String prefixedToken)
{
checkArgument(hasPrefix(prefixedToken));
return prefixedToken.substring(9);
}
private static boolean hasPrefix(String token)
{
return token != null && token.length() > 9 && token.charAt(8) == ':';
}
private static String prefix(String backend, String backendToken)
{
return backend + ":" + backendToken;
}
private Object getFailedResponse(String requestName, TStatusCode statusCode, String errorMessage)
throws RemoteException
{
char first = requestName.charAt(0);
String capitalizedRequestName = Character.isUpperCase(first) ? requestName :
(Character.toUpperCase(first) + requestName.substring(1));
try {
Class<?> responseClass = Class.forName("org.dcache.srm.v2_2."+capitalizedRequestName+"Response");
Constructor<?> responseConstructor = responseClass.getConstructor();
Object response;
try {
response = responseConstructor.newInstance();
} catch (InvocationTargetException e) {
Throwables.propagateIfPossible(e, Exception.class);
throw new RuntimeException("Unexpected exception", e);
}
try {
Method setReturnStatus = responseClass
.getMethod("setReturnStatus", TReturnStatus.class);
setReturnStatus.setAccessible(true);
try {
setReturnStatus.invoke(response, new TReturnStatus(statusCode, errorMessage));
} catch (InvocationTargetException e) {
Throwables.propagateIfPossible(e, Exception.class);
throw new RuntimeException("Unexpected exception", e);
}
} catch (Exception e) {
LOGGER.trace("getFailedResponse invocation failed", e);
Method setStatusCode = responseClass.getMethod("setStatusCode", TStatusCode.class);
setStatusCode.setAccessible(true);
setStatusCode.invoke(response, statusCode);
Method setExplanation = responseClass.getMethod("setExplanation", String.class);
setExplanation.setAccessible(true);
setExplanation.invoke(response, errorMessage);
}
return response;
} catch (Exception e) {
throw new RemoteException("Failed to generate SRM reply", e);
}
}
private interface RequestLogger
{
void request(String requestName, Object request);
void response(String requestName, Object request, Object response, Subject user, long time);
}
public class AccessLogger implements RequestLogger
{
private final Logger ACCESS_LOGGER = LoggerFactory.getLogger("org.dcache.access.srm");
@Override
public void request(String requestName, Object request)
{
}
@Override
public void response(String requestName, Object request, Object response, Subject user, long time)
{
if (ACCESS_LOGGER.isErrorEnabled()) {
TReturnStatus status = getReturnStatus(response);
boolean isFailure = status != null && FAILURES.contains(status.getStatusCode());
if (!isFailure && !ACCESS_LOGGER.isInfoEnabled()) {
return;
}
NetLoggerBuilder.Level level = isFailure ? NetLoggerBuilder.Level.ERROR : NetLoggerBuilder.Level.INFO;
NetLoggerBuilder log = new NetLoggerBuilder(level, "org.dcache.srm.request").omitNullValues();
log.add("session", JDC.getSession());
log.add("socket.remote", Axis.getRemoteSocketAddress());
log.add("request.method", requestName);
log.add("user.dn", Axis.getDN().orElse("-"));
if (user != null) {
log.add("user.mapped", user);
}
String requestToken = getRequestToken(request, response);
if (requestToken != null) {
log.add("request.token", requestToken);
} else {
log.add("request.surl", getSurl(request));
}
logOperationSpecific(log, requestName, request, response);
if (status != null) {
log.add("status.code", status.getStatusCode());
log.add("status.explanation", status.getExplanation());
}
log.add("client-info", Axis.getRequestHeader("ClientInfo"));
log.add("user-agent", Axis.getUserAgent());
log.toLogger(ACCESS_LOGGER);
}
}
private void logOperationSpecific(NetLoggerBuilder log, String operation,
Object request, Object response)
{
switch (operation) {
case "srmAbortFiles":
log(log, (SrmAbortFilesRequest) request, (SrmAbortFilesResponse) response);
break;
case "srmAbortRequest":
log(log, (SrmAbortRequestRequest) request, (SrmAbortRequestResponse) response);
break;
case "srmBringOnline":
log(log, (SrmBringOnlineRequest) request, (SrmBringOnlineResponse) response);
break;
case "srmChangeSpaceForFiles":
log(log, (SrmChangeSpaceForFilesRequest) request, (SrmChangeSpaceForFilesResponse) response);
break;
case "srmCheckPermission":
log(log, (SrmCheckPermissionRequest) request, (SrmCheckPermissionResponse) response);
break;
case "srmCopy":
log(log, (SrmCopyRequest) request, (SrmCopyResponse) response);
break;
case "srmExtendFileLifeTimeInSpace":
log(log, (SrmExtendFileLifeTimeInSpaceRequest) request, (SrmExtendFileLifeTimeInSpaceResponse) response);
break;
case "srmExtendFileLifeTime":
log(log, (SrmExtendFileLifeTimeRequest) request, (SrmExtendFileLifeTimeResponse) response);
break;
case "srmGetPermission":
log(log, (SrmGetPermissionRequest) request, (SrmGetPermissionResponse) response);
break;
case "srmGetRequestSummary":
log(log, (SrmGetRequestSummaryRequest) request, (SrmGetRequestSummaryResponse) response);
break;
case "srmGetRequestTokens":
log(log, (SrmGetRequestTokensRequest) request, (SrmGetRequestTokensResponse) response);
break;
case "srmGetSpaceMetaData":
log(log, (SrmGetSpaceMetaDataRequest) request, (SrmGetSpaceMetaDataResponse) response);
break;
case "srmGetSpaceTokens":
log(log, (SrmGetSpaceTokensRequest) request, (SrmGetSpaceTokensResponse) response);
break;
case "srmGetTransferProtocols":
log(log, (SrmGetTransferProtocolsRequest) request, (SrmGetTransferProtocolsResponse) response);
break;
case "srmLs":
log(log, (SrmLsRequest) request, (SrmLsResponse) response);
break;
case "srmMkdir":
log(log, (SrmMkdirRequest) request, (SrmMkdirResponse) response);
break;
case "srmMv":
log(log, (SrmMvRequest) request, (SrmMvResponse) response);
break;
case "srmPing":
log(log, (SrmPingRequest) request, (SrmPingResponse) response);
break;
case "srmPrepareToGet":
log(log, (SrmPrepareToGetRequest) request, (SrmPrepareToGetResponse) response);
break;
case "srmPrepareToPut":
log(log, (SrmPrepareToPutRequest) request, (SrmPrepareToPutResponse) response);
break;
case "srmPurgeFromSpace":
log(log, (SrmPurgeFromSpaceRequest) request, (SrmPurgeFromSpaceResponse) response);
break;
case "srmPutDone":
log(log, (SrmPutDoneRequest) request, (SrmPutDoneResponse) response);
break;
case "srmReleaseFiles":
log(log, (SrmReleaseFilesRequest) request, (SrmReleaseFilesResponse) response);
break;
case "srmReleaseSpace":
log(log, (SrmReleaseSpaceRequest) request, (SrmReleaseSpaceResponse) response);
break;
case "srmReserveSpace":
log(log, (SrmReserveSpaceRequest) request, (SrmReserveSpaceResponse) response);
break;
case "srmResumeRequest":
log(log, (SrmResumeRequestRequest) request, (SrmResumeRequestResponse) response);
break;
case "srmRmdir":
log(log, (SrmRmdirRequest) request, (SrmRmdirResponse) response);
break;
case "srmRm":
log(log, (SrmRmRequest) request, (SrmRmResponse) response);
break;
case "srmSetPermission":
log(log, (SrmSetPermissionRequest) request, (SrmSetPermissionResponse) response);
break;
case "srmStatusOfBringOnlineRequest":
log(log, (SrmStatusOfBringOnlineRequestRequest) request, (SrmStatusOfBringOnlineRequestResponse) response);
break;
case "srmStatusOfChangeSpaceForFilesRequest":
log(log, (SrmStatusOfChangeSpaceForFilesRequestRequest) request, (SrmStatusOfChangeSpaceForFilesRequestResponse) response);
break;
case "srmStatusOfCopyRequest":
log(log, (SrmStatusOfCopyRequestRequest) request, (SrmStatusOfCopyRequestResponse) response);
break;
case "srmStatusOfGetRequest":
log(log, (SrmStatusOfGetRequestRequest) request, (SrmStatusOfGetRequestResponse) response);
break;
case "srmStatusOfLsRequest":
log(log, (SrmStatusOfLsRequestRequest) request, (SrmStatusOfLsRequestResponse) response);
break;
case "srmStatusOfPutRequest":
log(log, (SrmStatusOfPutRequestRequest) request, (SrmStatusOfPutRequestResponse) response);
break;
case "srmStatusOfReserveSpaceRequest":
log(log, (SrmStatusOfReserveSpaceRequestRequest) request, (SrmStatusOfReserveSpaceRequestResponse) response);
break;
case "srmStatusOfUpdateSpaceRequest":
log(log, (SrmStatusOfUpdateSpaceRequestRequest) request, (SrmStatusOfUpdateSpaceRequestResponse) response);
break;
case "srmSuspendRequest":
log(log, (SrmSuspendRequestRequest) request, (SrmSuspendRequestResponse) response);
break;
case "srmUpdateSpace":
log(log, (SrmUpdateSpaceRequest) request, (SrmUpdateSpaceResponse) response);
break;
default:
LOGGER.error("Unknown SRM request {}", operation);
}
}
private void log(NetLoggerBuilder log, Object request, Object response)
{
// by default, add no additional logging.
}
private void log(NetLoggerBuilder log, SrmAbortFilesRequest request,
SrmAbortFilesResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSURLs(),
ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTSURLReturnStatus::getStatusArray,
TSURLReturnStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmRmRequest request, SrmRmResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSURLs(),
ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTSURLReturnStatus::getStatusArray,
TSURLReturnStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmLsRequest request, SrmLsResponse response)
{
logCountAndOffset(log, request.getCount(), request.getOffset());
log.addSingleValue("request.surl", request.getArrayOfSURLs(), ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getDetails(),
ArrayOfTMetaDataPathDetail::getPathDetailArray, TMetaDataPathDetail::getStatus);
}
private void log(NetLoggerBuilder log, SrmStatusOfLsRequestRequest request,
SrmStatusOfLsRequestResponse response)
{
logCountAndOffset(log, request.getCount(), request.getOffset());
logFileStatus(log, response.getDetails(),
ArrayOfTMetaDataPathDetail::getPathDetailArray, TMetaDataPathDetail::getStatus);
}
private void log(NetLoggerBuilder log, SrmPrepareToGetRequest request,
SrmPrepareToGetResponse response)
{
log.add("request.pin", request.getDesiredPinLifeTime());
log.add("request.lifetime", request.getDesiredTotalRequestTime());
log.addSingleValue("request.surl", request.getArrayOfFileRequests(),
ArrayOfTGetFileRequest::getRequestArray, TGetFileRequest::getSourceSURL);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTGetRequestFileStatus::getStatusArray, TGetRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmStatusOfGetRequestRequest request,
SrmStatusOfGetRequestResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSourceSURLs(),
ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTGetRequestFileStatus::getStatusArray, TGetRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmPrepareToPutRequest request,
SrmPrepareToPutResponse response)
{
log.add("request.pin", request.getDesiredPinLifeTime());
log.add("request.lifetime", request.getDesiredTotalRequestTime());
log.addSingleValue("request.surl", request.getArrayOfFileRequests(),
ArrayOfTPutFileRequest::getRequestArray, TPutFileRequest::getTargetSURL);
ArrayOfTPutRequestFileStatus statuses = response.getArrayOfFileStatuses();
log.addSingleValue("turl", statuses, ArrayOfTPutRequestFileStatus::getStatusArray,
TPutRequestFileStatus::getTransferURL);
logFileStatus(log, statuses, ArrayOfTPutRequestFileStatus::getStatusArray,
TPutRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmStatusOfPutRequestRequest request, SrmStatusOfPutRequestResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfTargetSURLs(),
ArrayOfAnyURI::getUrlArray);
ArrayOfTPutRequestFileStatus statuses = response.getArrayOfFileStatuses();
log.addSingleValue("turl", statuses, ArrayOfTPutRequestFileStatus::getStatusArray,
TPutRequestFileStatus::getTransferURL);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTPutRequestFileStatus::getStatusArray, TPutRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmPutDoneRequest request,
SrmPutDoneResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSURLs(), ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTSURLReturnStatus::getStatusArray, TSURLReturnStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmCopyRequest request,
SrmCopyResponse response)
{
log.addSingleValue("request.src-surl", request.getArrayOfFileRequests(), ArrayOfTCopyFileRequest::getRequestArray, TCopyFileRequest::getSourceSURL);
log.addSingleValue("request.dst-surl", request.getArrayOfFileRequests(), ArrayOfTCopyFileRequest::getRequestArray, TCopyFileRequest::getTargetSURL);
logFileStatus(log, response.getArrayOfFileStatuses(), ArrayOfTCopyRequestFileStatus::getStatusArray, TCopyRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmStatusOfCopyRequestRequest request, SrmStatusOfCopyRequestResponse response)
{
log.addSingleValue("request.src-surl", request.getArrayOfSourceSURLs(), ArrayOfAnyURI::getUrlArray);
log.addSingleValue("request.dst-surl", request.getArrayOfTargetSURLs(), ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(), ArrayOfTCopyRequestFileStatus::getStatusArray, TCopyRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmReleaseFilesRequest request,
SrmReleaseFilesResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSURLs(), ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTSURLReturnStatus::getStatusArray, TSURLReturnStatus::getStatus);
}
private void log(NetLoggerBuilder log, SrmBringOnlineRequest request,
SrmBringOnlineResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfFileRequests(),
ArrayOfTGetFileRequest::getRequestArray, TGetFileRequest::getSourceSURL);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTBringOnlineRequestFileStatus::getStatusArray,
TBringOnlineRequestFileStatus::getStatus);
}
private void log(NetLoggerBuilder log,
SrmStatusOfBringOnlineRequestRequest request,
SrmStatusOfBringOnlineRequestResponse response)
{
log.addSingleValue("request.surl", request.getArrayOfSourceSURLs(), ArrayOfAnyURI::getUrlArray);
logFileStatus(log, response.getArrayOfFileStatuses(),
ArrayOfTBringOnlineRequestFileStatus::getStatusArray,
TBringOnlineRequestFileStatus::getStatus);
}
private void logCountAndOffset(NetLoggerBuilder log, Integer count, Integer offset)
{
if (count != null || offset != null) {
StringBuilder sb = new StringBuilder();
if (count != null) {
sb.append(count);
}
if (offset != null) {
sb.append('@').append(offset);
}
log.add("limit", sb.toString());
}
}
private <U,A> void logFileStatus(NetLoggerBuilder log, U source, Function<U,A[]> toArray, Function <A,TReturnStatus> toFileStatus)
{
log.addSingleValue("file-status.code", source, toArray, toFileStatus.andThen(TReturnStatus::getStatusCode));
log.addSingleValue("file-status.explanation", source, toArray, toFileStatus.andThen(TReturnStatus::getExplanation));
}
}
public class CounterLogger implements RequestLogger
{
@Override
public void request(String requestName, Object request)
{
srmServerCounters.incrementRequests(request.getClass());
}
@Override
public void response(String requestName, Object request, Object response, Subject user, long time)
{
TReturnStatus status = getReturnStatus(response);
if (status != null && FAILURES.contains(status.getStatusCode())) {
srmServerCounters.incrementFailed(request.getClass());
}
}
}
private class RequestExecutionTimeGaugeLogger implements RequestLogger
{
@Override
public void request(String requestName, Object request)
{
}
@Override
public void response(String requestName, Object request, Object response, Subject user, long time)
{
srmServerGauges.update(request.getClass(), time);
}
}
private static String getSurl(Object request)
{
try {
Method getReturnStatus = request.getClass().getDeclaredMethod("getSURL");
Class<?> returnType = getReturnStatus.getReturnType();
if (org.apache.axis.types.URI.class.isAssignableFrom(returnType)) {
Object uri = getReturnStatus.invoke(request);
if (uri != null) {
return uri.toString();
}
}
} catch (NoSuchMethodException e) {
// Unfortunately, Java standard API provides no nice way of
// discovering if a method exists by reflection. This is perhaps
// the least ugly.
} catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.debug("Failed to extract SURL: {}", e.toString());
}
return null;
}
private static String getRequestToken(Object request, Object response)
{
String requestToken = getRequestToken(response);
if (requestToken != null) {
return requestToken;
}
requestToken = getRequestToken(request);
if (requestToken != null) {
return requestToken;
}
return null;
}
private static String getRequestToken(Object response)
{
try {
Method getReturnStatus = response.getClass().getDeclaredMethod("getRequestToken");
Class<?> returnType = getReturnStatus.getReturnType();
if (String.class.isAssignableFrom(returnType)) {
return (String) getReturnStatus.invoke(response);
}
} catch (NoSuchMethodException e) {
// Unfortunately, Java standard API provides no nice way of
// discovering if a method exists by reflection. This is perhaps
// the least ugly.
} catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.debug("Failed to extract request token: {}", e.toString());
}
return null;
}
private static TReturnStatus getReturnStatus(Object response)
{
try {
Method getReturnStatus = response.getClass().getDeclaredMethod("getReturnStatus");
Class<?> returnType = getReturnStatus.getReturnType();
if (TReturnStatus.class.isAssignableFrom(returnType)) {
return (TReturnStatus) getReturnStatus.invoke(response);
}
} catch (NoSuchMethodException e) {
// Unfortunately, Java standard API provides no nice way of
// discovering if a method exists by reflection. This is perhaps
// the least ugly.
} catch (InvocationTargetException | IllegalAccessException e) {
LOGGER.debug("Failed to extract status code: {}", e.toString());
}
return null;
}
}