package org.dcache.chimera.nfsv41.door.proxy;
import com.google.common.base.Stopwatch;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.net.HostAndPort;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.dcache.nfs.status.NfsIoException;
import org.dcache.nfs.v4.CompoundContext;
import org.dcache.nfs.v4.Layout;
import org.dcache.nfs.v4.NFS4Client;
import org.dcache.nfs.v4.NFS4State;
import org.dcache.nfs.v4.NFSv41DeviceManager;
import org.dcache.nfs.v4.client.GetDeviceListStub;
import org.dcache.nfs.v4.client.LayoutgetStub;
import org.dcache.nfs.v4.xdr.device_addr4;
import org.dcache.nfs.v4.xdr.deviceid4;
import org.dcache.nfs.v4.xdr.layoutiomode4;
import org.dcache.nfs.v4.xdr.layouttype4;
import org.dcache.nfs.v4.xdr.netaddr4;
import org.dcache.nfs.v4.xdr.nfsv4_1_file_layout4;
import org.dcache.nfs.v4.xdr.nfsv4_1_file_layout_ds_addr4;
import org.dcache.nfs.v4.xdr.stateid4;
import org.dcache.nfs.vfs.Inode;
import org.dcache.util.backoff.ExponentialBackoffAlgorithmFactory;
import org.dcache.util.backoff.IBackoffAlgorithm;
import org.dcache.utils.net.InetSocketAddresses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.dcache.chimera.nfsv41.door.ExceptionUtils.asNfsException;
public class NfsProxyIoFactory implements ProxyIoFactory {
private static final TimeUnit MAX_CONNECT_TIMEOUT_UNIT = TimeUnit.SECONDS;
private static final long MAX_CONNECT_TIMEOUT = 15;
private static final TimeUnit TIMEOUT_STEP_UNIT = TimeUnit.MILLISECONDS;
private static final long TIMEOUT_STEP = 100;
private static final Logger _log = LoggerFactory.getLogger(NfsProxyIoFactory.class);
private final Cache<stateid4, ProxyIoAdapter> _proxyIO
= CacheBuilder.newBuilder()
.build();
private final NFSv41DeviceManager deviceManager;
private final ExponentialBackoffAlgorithmFactory backoffFactory;
public NfsProxyIoFactory(NFSv41DeviceManager deviceManager) {
this.deviceManager = deviceManager;
backoffFactory = new ExponentialBackoffAlgorithmFactory();
backoffFactory.setMinDelay(TIMEOUT_STEP);
backoffFactory.setMinUnit(TIMEOUT_STEP_UNIT);
}
@Override
public ProxyIoAdapter getOrCreateProxy(Inode inode, stateid4 stateid, CompoundContext context, boolean isWrite) throws IOException {
try {
return _proxyIO.get(stateid,
() -> {
final NFS4Client nfsClient;
if (context.getMinorversion() == 1) {
nfsClient = context.getSession().getClient();
} else {
nfsClient = context.getStateHandler().getClientIdByStateId(stateid);
}
final NFS4State state = nfsClient.state(stateid);
final ProxyIoAdapter adapter = createIoAdapter(inode, stateid, context, isWrite);
state.addDisposeListener(s -> {
tryToClose(adapter);
_proxyIO.invalidate(s.stateid());
});
return adapter;
});
} catch (ExecutionException e) {
Throwable t = e.getCause();
_log.debug("failed to create IO adapter: {}", t.getMessage());
throw asNfsException(t, NfsIoException.class);
}
}
@Override
public ProxyIoAdapter createIoAdapter (Inode inode, stateid4 stateid, CompoundContext context, boolean isWrite) throws IOException {
_log.info("creating new proxy-io adapter for {} {}", inode, context.getRemoteSocketAddress().getAddress().getHostAddress());
Layout layout = deviceManager.layoutGet(context, inode,
layouttype4.LAYOUT4_NFSV4_1_FILES,
isWrite ? layoutiomode4.LAYOUTIOMODE4_RW : layoutiomode4.LAYOUTIOMODE4_READ, stateid);
// we assume only one segment as dcache doesn't support striping
nfsv4_1_file_layout4 fileLayoutSegment = LayoutgetStub.decodeLayoutId(layout.getLayoutSegments()[0].lo_content.loc_body);
deviceid4 dsId = fileLayoutSegment.nfl_deviceid;
device_addr4 deviceAddr = deviceManager.getDeviceInfo(context, dsId, layouttype4.LAYOUT4_NFSV4_1_FILES);
nfsv4_1_file_layout_ds_addr4 nfs4DeviceAddr = GetDeviceListStub.decodeFileDevice(deviceAddr.da_addr_body);
Stopwatch connectStopwatch = Stopwatch.createStarted();
IBackoffAlgorithm backoff = backoffFactory.getAlgorithm();
retry: while (true) {
long timeout = backoff.getWaitDuration();
if (timeout == IBackoffAlgorithm.NO_WAIT) {
break;
}
// we assume that only device points only to one server
for (netaddr4 na : nfs4DeviceAddr.nflda_multipath_ds_list[0].value) {
if (connectStopwatch.elapsed(MAX_CONNECT_TIMEOUT_UNIT) > MAX_CONNECT_TIMEOUT) {
break retry;
}
if (na.na_r_netid.equals("tcp") || na.na_r_netid.equals("tcp6")) {
InetSocketAddress poolSocketAddress = InetSocketAddresses.forUaddrString(na.na_r_addr);
InetAddress address = poolSocketAddress.getAddress();
if (!isHostLocal(address)) {
try {
return new NfsProxyIo(poolSocketAddress, context.getRemoteSocketAddress(), inode, stateid, timeout, TIMEOUT_STEP_UNIT);
} catch (IOException e) {
_log.warn("Failed to connect to remote mover {} : {}", address, e.getMessage());
}
}
}
}
_log.warn("Failed to connect to pool {} within {}, Retrying...", toString(nfs4DeviceAddr.nflda_multipath_ds_list[0].value), connectStopwatch);
}
_log.error("Failed to connect to pool {} within {}, Giving up!", toString(nfs4DeviceAddr.nflda_multipath_ds_list[0].value), connectStopwatch);
deviceManager.layoutReturn(context, stateid);
context.getStateHandler().getClientIdByStateId(stateid).releaseState(layout.getStateid());
throw new NfsIoException("can't connect to pool");
}
private static boolean isHostLocal(InetAddress address) {
return address.isAnyLocalAddress() || address.isLoopbackAddress() || address.isLinkLocalAddress();
}
// FIXME: move this into generic NFS code
private static String toString(netaddr4[] netaddr) {
return Arrays.stream(netaddr)
.filter(n -> (n.na_r_netid.equals("tcp") || n.na_r_netid.equals("tcp6")))
.map(n -> InetSocketAddresses.forUaddrString(n.na_r_addr))
.map(a -> HostAndPort.fromParts(a.getHostString(), a.getPort()))
.map(Object::toString)
.collect(Collectors.joining(",", "[", "]"));
}
private static void tryToClose(ProxyIoAdapter adapter) {
try {
adapter.close();
} catch (IOException e) {
_log.error("failed to close io adapter: ", e.getMessage());
}
}
@Override
public void shutdownAdapter(stateid4 stateid) {
}
@Override
public void shutdown() {
}
@Override
public void forEach(Consumer<ProxyIoAdapter> action) {
_proxyIO.asMap().values().forEach(action);
}
@Override
public int getCount() {
return (int)_proxyIO.size();
}
}