/* * Copyright 1999-2006 University of Chicago * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.dcache.ftp.client.extended; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.DataOutputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.nio.channels.ServerSocketChannel; import org.dcache.ftp.client.DataChannelAuthentication; import org.dcache.ftp.client.DataSink; import org.dcache.ftp.client.DataSource; import org.dcache.ftp.client.GridFTPSession; import org.dcache.ftp.client.HostPort; import org.dcache.ftp.client.HostPort6; import org.dcache.ftp.client.HostPortList; import org.dcache.ftp.client.Options; import org.dcache.ftp.client.RetrieveOptions; import org.dcache.ftp.client.Session; import org.dcache.ftp.client.dc.EBlockImageDCWriter; import org.dcache.ftp.client.dc.EBlockParallelTransferContext; import org.dcache.ftp.client.dc.ManagedSocketBox; import org.dcache.ftp.client.dc.SocketBox; import org.dcache.ftp.client.dc.SocketOperator; import org.dcache.ftp.client.dc.SocketPool; import org.dcache.ftp.client.dc.StripeContextManager; import org.dcache.ftp.client.dc.TransferContext; import org.dcache.ftp.client.dc.TransferThreadManager; import org.dcache.ftp.client.exception.ClientException; import org.dcache.ftp.client.exception.DataChannelException; import org.dcache.ftp.client.vanilla.FTPControlChannel; import org.dcache.ftp.client.vanilla.FTPServerFacade; import org.dcache.util.NetworkUtils; import org.dcache.util.PortRange; public class GridFTPServerFacade extends FTPServerFacade { private static final Logger logger = LoggerFactory.getLogger(GridFTPServerFacade.class); // utility alias to session protected GridFTPSession gSession = null; protected SocketPool socketPool = null; protected TransferThreadManager transferThreadManager = null; // current transfer, if striped retrieve, is associated to this manager // (striped store does not suffer for EOD complications and don't need // manager) protected StripeContextManager stripeRetrContextManager = null; public GridFTPServerFacade(FTPControlChannel remoteControlChannel) { super(remoteControlChannel); gSession = new GridFTPSession(); session = gSession; // make sure this doesn't get used dataChannelFactory = null; socketPool = new SocketPool(); transferThreadManager = createTransferThreadManager(); } public void setDataChannelProtection(int protection) { gSession.dataChannelProtection = protection; } public void setDataChannelAuthentication(DataChannelAuthentication authentication) { gSession.dataChannelAuthentication = authentication; } @Override public void setOptions(Options opts) { if (opts instanceof RetrieveOptions) { gSession.parallel = ((RetrieveOptions) opts).getStartingParallelism(); logger.debug("parallelism set to " + gSession.parallel); } } @Override protected void transferAbort() { if (session.serverMode == Session.SERVER_PASSIVE) { unblockServer(); transferThreadManager.stopTaskThread(); } } /** * All sockets opened when this server was active * should send a special EBlock header before closing. */ private void closeOutgoingSockets() throws ClientException { SocketOperator op = new SocketOperator() { @Override public void operate(SocketBox sb) throws Exception { if (((ManagedSocketBox) sb).isReusable()) { Socket s = sb.getSocket(); if (s != null) { // write the closing Eblock and close the socket EBlockImageDCWriter.close( new DataOutputStream(s.getOutputStream())); } } } }; try { socketPool.applyToAll(op); } catch (IOException e) { // ignore - sometimes server might close the socket } catch (Exception e) { ClientException ce = new ClientException(ClientException.SOCKET_OP_FAILED); ce.setRootCause(e); throw ce; } } @Override public void setActive(HostPort hp) throws UnknownHostException, ClientException, IOException { if (logger.isDebugEnabled()) { logger.debug("hostport: " + hp.getHost() + " " + hp.getPort()); } if (session.serverMode == Session.SERVER_ACTIVE) { closeOutgoingSockets(); } socketPool.flush(); session.serverMode = Session.SERVER_ACTIVE; // may be needed later, if parallelism increases and // new connections need to be open this.remoteServerAddress = hp; transferThreadManager.activeConnect(hp, gSession.parallel); } public void setStripedActive(HostPortList hpl) throws UnknownHostException, IOException { if (hpl == null) { throw new IllegalArgumentException("null HostPortList"); } int stripes = hpl.size(); if (stripes < 1) { throw new IllegalArgumentException("empty HostPortList"); } socketPool.flush(); // = new SocketBox[pathes * stripes]; //create context manager that will be used by retrieve() this.stripeRetrContextManager = new StripeContextManager(stripes, socketPool, this); int pathes = gSession.parallel; gSession.serverMode = GridFTPSession.SERVER_EACT; for (int stripe = 0; stripe < stripes; stripe++) { transferThreadManager.activeConnect(hpl.get(stripe), pathes); } } @Override public HostPort setPassive(PortRange range, int queue) throws IOException { // remove existing sockets, if any socketPool.flush(); return super.setPassive(range, queue); } public HostPortList setStripedPassive() throws IOException { return setStripedPassive(new PortRange(0), DEFAULT_QUEUE); } public HostPortList setStripedPassive(PortRange range, int queue) throws IOException { // remove existing sockets, if any socketPool.flush(); if (serverSocket == null) { ServerSocketChannel channel = ServerSocketChannel.open(); range.bind(channel.socket(), queue); serverSocket = channel.socket(); } gSession.serverMode = GridFTPSession.SERVER_EPAS; gSession.serverAddressList = new HostPortList(); String address = NetworkUtils.getLocalAddress(InetAddress.getByName(remoteControlChannel.getHost())).getHostAddress(); int localPort = serverSocket.getLocalPort(); HostPort hp = null; if (remoteControlChannel.isIPv6()) { String version = HostPort6.getIPAddressVersion(address); hp = new HostPort6(version, address, localPort); } else { hp = new HostPort(address, localPort); } gSession.serverAddressList.add(hp); logger.debug("started single striped passive server at port " + gSession.serverAddressList.get(0).getPort()); return gSession.serverAddressList; } /** * Store the data from the data channel to the data sink. * Does not block. * If operation fails, exception might be thrown via local control channel. * * @param sink source of data **/ @Override public void store(DataSink sink) { try { localControlChannel.resetReplyCount(); if (session.transferMode != GridFTPSession.MODE_EBLOCK) { // // no EBLOCK // EBlockParallelTransferContext context = (EBlockParallelTransferContext) createTransferContext(); context.setEodsTotal(0); if (session.serverMode == Session.SERVER_PASSIVE) { transferThreadManager.passiveConnect( sink, context, 1, serverSocket); } else { //1 non reusable connection transferThreadManager.startTransfer( sink, context, 1, ManagedSocketBox.NON_REUSABLE); } } else if ( session.serverMode != GridFTPSession.SERVER_EPAS && session.serverMode != GridFTPSession.SERVER_PASSIVE) { // // EBLOCK, local server not passive // exceptionToControlChannel( new DataChannelException( DataChannelException.BAD_SERVER_MODE), "refusing to store with active mode"); } else { // // EBLOCK, local server passive // // data channels will // share this transfer context EBlockParallelTransferContext context = (EBlockParallelTransferContext) createTransferContext(); // we are the passive side, so we don't really get to decide // how many connections will be used int willReuseConnections = socketPool.countFree(); int needNewConnections = 0; if (gSession.parallel > willReuseConnections) { needNewConnections = gSession.parallel - willReuseConnections; } logger.debug("will reuse " + willReuseConnections + " connections and start " + needNewConnections + " new ones."); transferThreadManager.startTransfer( sink, context, willReuseConnections, ManagedSocketBox.REUSABLE); if (needNewConnections > 0) { transferThreadManager.passiveConnect( sink, context, needNewConnections, serverSocket); } } } catch (Exception e) { exceptionToControlChannel(e, "ocurred during store()"); } } /** * Retrieve the data from the data source and write to the data channel. * This method does not block. * If operation fails, exception might be thrown via local control channel. * * @param source source of data **/ @Override public void retrieve(DataSource source) { try { localControlChannel.resetReplyCount(); if (session.transferMode != GridFTPSession.MODE_EBLOCK) { // // No EBLOCK // EBlockParallelTransferContext context = (EBlockParallelTransferContext) createTransferContext(); context.setEodsTotal(0); logger.debug("starting outgoing transfer without mode E"); if (session.serverMode == Session.SERVER_PASSIVE) { transferThreadManager.passiveConnect(source, context, serverSocket); } else { transferThreadManager.startTransfer( source, context, 1, ManagedSocketBox.NON_REUSABLE); } } else if (session.serverMode == Session.SERVER_ACTIVE) { // // EBLOCK, no striping // // data channels will share this transfer context EBlockParallelTransferContext context = (EBlockParallelTransferContext) createTransferContext(); int total = gSession.parallel; //we should send as many EODS as there are parallel streams context.setEodsTotal(total); int free = socketPool.countFree(); int willReuseConnections = (total > free) ? free : total; int willCloseConnections = (free > total) ? free - total : 0; int needNewConnections = (total > free) ? total - free : 0; logger.debug("will reuse " + willReuseConnections + " connections, start " + needNewConnections + " new ones, and close " + willCloseConnections); if (needNewConnections > 0) { transferThreadManager.activeConnect(this.remoteServerAddress, needNewConnections); } if (willCloseConnections > 0) { transferThreadManager.activeClose(context, willCloseConnections); } transferThreadManager.startTransfer( source, context, willReuseConnections + needNewConnections, ManagedSocketBox.REUSABLE); } else if (session.serverMode == GridFTPSession.SERVER_EACT) { // // EBLOCK, striping // if (stripeRetrContextManager == null) { throw new IllegalStateException(); } int stripes = stripeRetrContextManager.getStripes(); for (int stripe = 0; stripe < stripes; stripe++) { EBlockParallelTransferContext context = stripeRetrContextManager.getStripeContext(stripe); context.setEodsTotal(gSession.parallel); transferThreadManager.startTransfer( source, context, gSession.parallel, ManagedSocketBox.REUSABLE); } } else { // // EBLOCK and local server not active // throw new DataChannelException( DataChannelException.BAD_SERVER_MODE); } } catch (Exception e) { exceptionToControlChannel(e, "ocurred during retrieve()"); } } //override @Override public void abort() throws IOException { super.abort(); if (socketPool != null) { socketPool.flush(); } } //override @Override public void close() throws IOException { super.close(); if (transferThreadManager != null) { transferThreadManager.close(); } } @Override protected TransferContext createTransferContext() { EBlockParallelTransferContext context = new EBlockParallelTransferContext(); context.setSocketPool(socketPool); context.setTransferThreadManager(this.transferThreadManager); return context; } public TransferThreadManager createTransferThreadManager() { return new TransferThreadManager(socketPool, this, localControlChannel, gSession); } }