/* dCache - http://www.dcache.org/
*
* Copyright (C) 2013-2015 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 org.dcache.xrootd.pool;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import diskCacheV111.util.CacheException;
import dmg.cells.nucleus.CellPath;
import org.dcache.pool.movers.NettyMover;
import org.dcache.pool.movers.NettyTransferService;
import org.dcache.util.NetworkUtils;
import org.dcache.vehicles.XrootdDoorAdressInfoMessage;
import org.dcache.vehicles.XrootdProtocolInfo;
import org.dcache.xrootd.core.XrootdDecoder;
import org.dcache.xrootd.core.XrootdEncoder;
import org.dcache.xrootd.core.XrootdHandshakeHandler;
import org.dcache.xrootd.plugins.ChannelHandlerFactory;
import org.dcache.xrootd.protocol.XrootdProtocol;
import org.dcache.xrootd.stream.ChunkedResponseWriteHandler;
/**
* xrootd transfer service.
*
* The transfer service uses a Netty server. The Netty server is started dynamically
* as soon as any xrootd movers have been executed. The server shuts down once the
* last xrootd movers terminates.
*
* Xrootd movers are registered with the Netty server using a UUID. The UUID is
* relayed to the door which includes it in the xrootd redirect sent to the client.
* The redirected client will include the UUID when connecting to the pool and
* serves as an one-time authorization token and as a means of binding the client
* request to the correct mover.
*
* A transfer is considered to have succeeded if at least one file was opened and
* all opened files were closed again.
*
* Open issues:
*
* * Write calls blocked on space allocation may starve read
* calls. This is because both are served by the same thread
* pool. This should be changed such that separate thread pools are
* used (may fix later).
*
* * Write messages are currently processed as one big message. If the
* client chooses to upload a huge file as a single write message,
* then the pool will run out of memory. We can fix this by breaking
* a write message into many small blocks. The old mover suffers
* from the same problem (may fix later).
*
* * At least for vector read, the behaviour when reading beyond the
* end of the file is wrong.
*/
public class XrootdTransferService extends NettyTransferService<XrootdProtocolInfo>
{
private static final Logger LOGGER =
LoggerFactory.getLogger(XrootdTransferService.class);
private int maxFrameSize;
private List<ChannelHandlerFactory> plugins;
private Map<String, String> queryConfig;
public XrootdTransferService()
{
super("xrootd");
}
@Required
public void setPlugins(List<ChannelHandlerFactory> plugins)
{
this.plugins = plugins;
}
public List<ChannelHandlerFactory> getPlugins()
{
return plugins;
}
@Required
public void setMaxFrameSize(int maxFrameSize)
{
this.maxFrameSize = maxFrameSize;
}
public int getMaxFrameSize()
{
return maxFrameSize;
}
public Map<String, String> getQueryConfig()
{
return queryConfig;
}
@Required
public void setQueryConfig(Map<String, String> queryConfig)
{
this.queryConfig = queryConfig;
}
@Override
protected UUID createUuid(XrootdProtocolInfo protocolInfo)
{
return protocolInfo.getUUID();
}
/**
* Sends our address to the door. Copied from the old xrootd mover.
*/
@Override
protected void sendAddressToDoor(NettyMover<XrootdProtocolInfo> mover, int port)
throws SocketException, CacheException
{
XrootdProtocolInfo protocolInfo = mover.getProtocolInfo();
InetAddress localIP = NetworkUtils.getLocalAddress(protocolInfo.getSocketAddress().getAddress());
CellPath cellpath = protocolInfo.getXrootdDoorCellPath();
XrootdDoorAdressInfoMessage doorMsg =
new XrootdDoorAdressInfoMessage(protocolInfo.getXrootdFileHandle(), new InetSocketAddress(localIP, port));
doorStub.notify(cellpath, doorMsg);
LOGGER.debug("sending redirect {} to Xrootd-door {}", localIP, cellpath);
}
@Override
protected void initChannel(Channel ch) throws Exception
{
super.initChannel(ch);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("handshake",
new XrootdHandshakeHandler(XrootdProtocol.DATA_SERVER));
pipeline.addLast("encoder", new XrootdEncoder());
pipeline.addLast("decoder", new XrootdDecoder());
if (LOGGER.isDebugEnabled()) {
pipeline.addLast("logger", new LoggingHandler());
}
for (ChannelHandlerFactory plugin: plugins) {
pipeline.addLast("plugin:" + plugin.getName(),
plugin.createHandler());
}
pipeline.addLast("timeout", new IdleStateHandler(0,
0,
clientIdleTimeout,
clientIdleTimeoutUnit));
pipeline.addLast("chunkedWriter", new ChunkedResponseWriteHandler());
pipeline.addLast("transfer", new XrootdPoolRequestHandler(this, maxFrameSize, queryConfig));
}
}