/*
* 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.dcache;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.dao.DataAccessException;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import dmg.cells.nucleus.CellAddressCore;
import dmg.cells.nucleus.CellIdentityAware;
import dmg.cells.nucleus.CellMessageReceiver;
import dmg.cells.nucleus.CellPath;
import dmg.cells.nucleus.NoRouteToCellException;
import dmg.cells.zookeeper.PathChildrenCache;
import org.dcache.cells.CellStub;
import org.dcache.cells.CuratorFrameworkAware;
import org.dcache.srm.AbstractStorageElement;
import org.dcache.srm.SRM;
import org.dcache.srm.SRMException;
import org.dcache.srm.SRMInternalErrorException;
import org.dcache.srm.SRMInvalidPathException;
import org.dcache.srm.SRMNonEmptyDirectoryException;
import org.dcache.srm.SrmAbortTransfersRequest;
import org.dcache.srm.SrmAbortTransfersResponse;
import org.dcache.srm.SrmQueryPutRequest;
import org.dcache.srm.SrmQueryPutResponse;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.util.stream.Collectors.toList;
/**
* Subclass of the SRM main class implementing cross-cutting concerns in
* clustered SrmManager deployments.
*/
public class DcacheSrm extends SRM implements CuratorFrameworkAware, CellIdentityAware, CellMessageReceiver
{
private PathChildrenCache backends;
private CuratorFramework client;
private CellStub srmManagerStub;
private CellAddressCore address;
public DcacheSrm(Configuration config, AbstractStorageElement storage)
throws IOException, InterruptedException, DataAccessException
{
super(config, storage);
}
@Override
public void start() throws Exception
{
backends = new PathChildrenCache(client, "/dcache/srm/backends", true);
backends.start();
super.start();
}
@Override
public void stop() throws Exception
{
super.stop();
if (backends != null) {
backends.close();
}
}
@Override
public void setCellAddress(CellAddressCore address)
{
this.address = address;
}
@Override
public void setCuratorFramework(CuratorFramework client)
{
this.client = client;
}
@Required
public void setSrmManagerStub(CellStub srmManagerStub)
{
this.srmManagerStub = srmManagerStub;
}
@Override
public boolean isFileBusy(URI surl) throws SRMException
{
return super.isFileBusy(surl) || findRemoteUpload(surl, false).isPresent();
}
@Override
public boolean hasMultipleUploads(URI surl) throws SRMException
{
Preconditions.checkState(super.isFileBusy(surl),
"Must only be called while at least one local upload is active");
return super.hasMultipleUploads(surl) || findRemoteUpload(surl, false).isPresent();
}
@Override
public String getUploadFileId(URI surl) throws SRMException
{
String fileId = super.getUploadFileId(surl);
if (fileId != null) {
return fileId;
}
return findRemoteUpload(surl, false).map(SrmQueryPutResponse::getFileId).orElse(null);
}
@Override
public boolean abortTransfers(URI surl, String reason) throws SRMException
{
boolean isUploadAborted = abortLocalTransfers(surl, reason);
for (ListenableFuture<SrmAbortTransfersResponse> future :
queryRemotes(new SrmAbortTransfersRequest(surl, reason), SrmAbortTransfersResponse.class)) {
try {
isUploadAborted |= Uninterruptibles.getUninterruptibly(future).isUploadAborted();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (!(cause instanceof NoRouteToCellException)) {
Throwables.propagateIfPossible(cause, SRMException.class);
throw new SRMInternalErrorException("Failed to abort transfers", cause);
}
}
}
return isUploadAborted;
}
@Override
public void checkRemoveDirectory(URI surl) throws SRMException
{
super.checkRemoveDirectory(surl);
Optional<SrmQueryPutResponse> request = findRemoteUpload(surl, true);
if (request.isPresent()) {
if (request.get().getSurl().equals(surl)) {
throw new SRMInvalidPathException("Not a directory");
} else {
throw new SRMNonEmptyDirectoryException("Directory is not empty");
}
}
}
private boolean abortLocalTransfers(URI surl, String reason) throws SRMException
{
return super.abortTransfers(surl, reason);
}
private Optional<SrmQueryPutResponse> findRemoteUpload(URI surl, boolean isRecursive) throws SRMException
{
for (ListenableFuture<SrmQueryPutResponse> future :
queryRemotes(new SrmQueryPutRequest(surl), SrmQueryPutResponse.class)) {
try {
SrmQueryPutResponse response = Uninterruptibles.getUninterruptibly(future);
if (response.getSurl() != null && (isRecursive || response.getSurl().equals(surl))) {
return Optional.of(response);
}
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (!(cause instanceof NoRouteToCellException)) {
Throwables.propagateIfPossible(cause, SRMException.class);
throw new SRMInternalErrorException("Failed to abort transfers", cause);
}
}
}
return Optional.empty();
}
private <T extends Serializable> List<ListenableFuture<T>> queryRemotes(Serializable request, Class<T> response)
{
return Futures.inCompletionOrder(
backends.getCurrentData().stream()
.map(this::toCellAddress)
.filter(adr -> !address.equals(adr))
.map(CellPath::new)
.map(path -> srmManagerStub.send(path, request, response))
.collect(toList()));
}
private CellAddressCore toCellAddress(ChildData data)
{
return new CellAddressCore(new String(data.getData(), US_ASCII));
}
public SrmQueryPutResponse messageArrived(SrmQueryPutRequest msg)
{
return getActivePutFileRequests(msg.getSurl())
.min((a, b) -> a.getSurl().compareTo(b.getSurl()))
.map(r -> new SrmQueryPutResponse(r.getSurl(), r.getId(), r.getFileId()))
.orElseGet(SrmQueryPutResponse::new);
}
public SrmAbortTransfersResponse messageArrived(SrmAbortTransfersRequest msg) throws SRMException
{
return new SrmAbortTransfersResponse(msg.getSurl(), abortLocalTransfers(msg.getSurl(), msg.getReason()));
}
}