/* * 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; } }