com.emc.ecs.nfsclient.network.Connection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nfs-client Show documentation
Show all versions of nfs-client Show documentation
NFS Client for Java - provides read/write access to data on NFS servers. The current implementation supports only NFS version 3.
/**
* Copyright 2016 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file 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 com.emc.ecs.nfsclient.network;
import com.emc.ecs.nfsclient.rpc.RpcException;
import com.emc.ecs.nfsclient.rpc.RpcStatus;
import com.emc.ecs.nfsclient.rpc.Xdr;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioSocketChannelConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Each Connection instance manages a tcp connection. The class is used to send
* data and track the status of a connection.
*
* @author seibed
*/
public class Connection {
/**
* Key for getting the connection from the helper map.
*/
static final String CONNECTION_OPTION = "bourneLocalConn";
/**
* Key for getting the remote address from the helper map.
*/
static final String REMOTE_ADDRESS_OPTION = "remoteAddress";
/**
* The usual logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(Connection.class);
/**
* The value in milliseconds.
*/
private static final int CONNECT_TIMEOUT = 10000; // 10 seconds
/**
* internal sending queue (not tcp sending buffer) the rename/lookup/readdir
* has size between 256-516bytes, 5M can hold 10000 pending requests for
* data writing, each request is 512K and maximum 40 pending requests are
* allowed. (512k is the preferred size in a single request. For object >
* 512K, it should be split to 512K-size chunks.) the queue size need be
* 20M.
*/
private static final int MAX_SENDING_QUEUE_SIZE = 1 * 1024 * 1024 * 1024;// bytes - total
// 1G
/**
* Netty helper instance.
*/
private final ClientBootstrap _clientBootstrap;
/**
* Netty channel representing a tcp connection.
*/
private Channel _channel;
/**
* Netty helper instance.
*/
ChannelFuture _channelFuture = Channels.future(null, true);
/**
* The remote server address, in any form.
*/
private final String _remoteHost;
/**
* The remote server port being used.
*/
private final int _port;
/**
* -
* If
true
, use a privileged local port (below 1024) for RPC communication.
* -
* If
false
, use any non-privileged local port for RPC communication.
*
*/
private final boolean _usePrivilegedPort;
/**
* Store the ChannelFuture instances while they are in progress. The map is final, but the content will change.
*/
private final ConcurrentHashMap _futureMap = new ConcurrentHashMap();
/**
* Store the Xdr response instances while they are in progress. The map is final, but the content will change.
*/
private final ConcurrentHashMap _responseMap = new ConcurrentHashMap();
/**
* Simple enums for communicating connection states.
*
* @author seibed
*/
public enum State {
DISCONNECTED, CONNECTING, CONNECTED;
}
/**
* The current state.
*/
private State _state = State.DISCONNECTED;
/**
* @param remoteHost A unique name for the host to which the connection is being made.
* @param port The remote host port being used for the connection.
* @param usePrivilegedPort
*
* - If
true
, use a privileged port (below 1024)
* for RPC communication.
* - If
false
, use any non-privileged port for RPC
* communication.
*
*/
public Connection(String remoteHost, int port, boolean usePrivilegedPort) {
_remoteHost = remoteHost;
_port = port;
_usePrivilegedPort = usePrivilegedPort;
_clientBootstrap = new ClientBootstrap(NetMgr.getInstance().getFactory());
// Configure the client.
_clientBootstrap.setOption(REMOTE_ADDRESS_OPTION, new InetSocketAddress(_remoteHost, _port));
_clientBootstrap.setOption("connectTimeoutMillis", CONNECT_TIMEOUT); // set
// connection
// timeout
// value
// to
// 10
// seconds
_clientBootstrap.setOption("tcpNoDelay", true);
_clientBootstrap.setOption("keepAlive", true);
_clientBootstrap.setOption(CONNECTION_OPTION, this);
// Configure the pipeline factory.
_clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
/**
* Netty helper instance.
*/
private final ChannelHandler ioHandler = new ClientIOHandler(_clientBootstrap);
/* (non-Javadoc)
* @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline()
*/
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(new RPCRecordDecoder(), ioHandler);
}
});
}
/**
* Convenience getter method.
*
* @return The remote server internet address.
*/
public InetSocketAddress getRemoteAddress() {
return (InetSocketAddress) _clientBootstrap.getOption(REMOTE_ADDRESS_OPTION);
}
/**
* Convenience getter method.
*
* @return The state.
*/
public State getConnectionState() {
return _state;
}
/**
* Send a RPC request and wait until a response is received or timeout. The
* function will not retry. It is the responsibility of the application to
* do retry. The behaviours: a. If the tcp connection is not established
* yet: (1). sendAndWait will wait until the connection is established or
* timeout or network error occurs. (2). Once the connection is established,
* sendAndWait can send data. b. If the tcp connection is established: (1).
* the request is put in the internal queue of netty. Netty will send it
* asap. If there are already lots of pending requests in the queue,
* sendAndWait return error. (2). sendAndWait will wait until it gets a
* response from NFS server or timeout. (3). If the tcp connection is
* broken, the function return error with network error. c. If the tcp
* connection is broken suddenly: (1) the old sendAndWait will get the
* network error or timeout (2) The new sendAndWait will follow (a).
*
* @param timeout
* The timeout in seconds.
* @param xdrRequest
* The generic RPC data and protocol-specific data.
* @return The Xdr data for the response.
* @throws RpcException
*/
public Xdr sendAndWait(int timeout, Xdr xdrRequest) throws RpcException {
// no lock is required here.
// The status may be changed after the checking,
// or there exists a small window that the status is not consistent to
// the actual tcp connection state.
// Both above cases will not cause any issues.
if (_state.equals(State.CONNECTED) == false) {
_channelFuture.awaitUninterruptibly();
if (_channelFuture.isSuccess() == false) {
String msg = String.format("waiting for connection to be established, but failed %s",
getRemoteAddress());
LOG.error(msg);
// return RpcException, the exact reason should already be
// logged in IOHandler::exceptionCaught()
throw new RpcException(RpcStatus.NETWORK_ERROR, msg);
}
}
// check whether the internal queue of netty has enough spaces to hold
// the request
// False means that the too many pending requests are in the queue or
// the connection is closed.
if (_channel.isWritable() == false) {
String msg;
if (_channel.isConnected()) {
msg = String.format("too many pending requests for the connection: %s", getRemoteAddress());
} else {
msg = String.format("the connection is broken: %s", getRemoteAddress());
}
// too many pending request are in the queue, return error
throw new RpcException(RpcStatus.NETWORK_ERROR, msg);
}
// put the request into a map for timeout management
ChannelFuture timeoutFuture = Channels.future(_channel);
Integer xid = Integer.valueOf(xdrRequest.getXid());
_futureMap.put(xid, timeoutFuture);
// put the request into the queue of the netty, netty will send data
// asynchronously
RecordMarkingUtil.putRecordMarkingAndSend(_channel, xdrRequest);
timeoutFuture.awaitUninterruptibly(timeout, TimeUnit.SECONDS);
// remove the response from timeout maps
Xdr response = _responseMap.remove(xid);
_futureMap.remove(xid);
if (timeoutFuture.isSuccess() == false) {
LOG.warn("cause:", timeoutFuture.getCause());
if (timeoutFuture.isDone()) {
String msg = String.format("tcp IO error on the connection: %s", getRemoteAddress());
throw new RpcException(RpcStatus.NETWORK_ERROR, msg);
} else {
String msg = String.format("rpc request timeout on the connection: %s", getRemoteAddress());
throw new RpcException(RpcStatus.NETWORK_ERROR, msg);
}
}
return response;
}
/**
* If there is no current connection, start a new tcp connection asynchronously.
*
* @throws RpcException
*/
protected void connect() throws RpcException {
if (_state.equals(State.CONNECTED)) {
return;
}
final ChannelFuture oldChannelFuture = _channelFuture;
if (LOG.isDebugEnabled()) {
String logPrefix = _usePrivilegedPort ? "usePrivilegedPort " : "";
LOG.debug("{}connecting to {}", logPrefix, getRemoteAddress());
}
_state = State.CONNECTING;
if (_usePrivilegedPort) {
_channel = bindToPrivilegedPort();
_channelFuture = _channel.connect(getRemoteAddress());
} else {
_channelFuture = _clientBootstrap.connect();
_channel = _channelFuture.getChannel();
}
NioSocketChannelConfig cfg = (NioSocketChannelConfig) _channel.getConfig();
cfg.setWriteBufferHighWaterMark(MAX_SENDING_QUEUE_SIZE);
_channelFuture.addListener(new ChannelFutureListener() {
/* (non-Javadoc)
* @see org.jboss.netty.channel.ChannelFutureListener#operationComplete(org.jboss.netty.channel.ChannelFuture)
*/
public void operationComplete(ChannelFuture future) {
if (_channelFuture.isSuccess()) {
_state = State.CONNECTED;
oldChannelFuture.setSuccess();
} else {
_state = State.DISCONNECTED;
oldChannelFuture.cancel();
}
}
});
}
/**
* This is called when the application is shutdown or the channel is closed.
*/
protected void shutdown() {
if (_channel != null) {
_channel.close();
}
}
/**
* This is called when the connection should be closed.
*/
protected void close() {
_state = State.DISCONNECTED;
shutdown();
// remove the connection from map
NetMgr.getInstance().dropConnection(InetSocketAddress.createUnresolved(_remoteHost, _port));
// notify all the pending requests in the timeout map
notifyAllPendingSenders("Channel closed, connection closing.");
}
/**
* Update the response map with the response and notify the thread waiting for the
* response. Do nothing if the future has been removed.
*
* @param xid
* @param response
*/
protected void notifySender(Integer xid, Xdr response) {
ChannelFuture future = _futureMap.get(xid);
if (future != null) {
_responseMap.put(xid, response);
future.setSuccess();
}
}
/**
* Notify all the senders of all pending requests
*/
protected void notifyAllPendingSenders(String message) {
for (ChannelFuture future : _futureMap.values()) {
future.setFailure(new Error(message));
}
}
/**
* This attempts to bind to privileged ports, starting with 1023 and working downwards, and returns when the first binding succeeds.
*
*
* Some NFS servers apparently may require that some requests originate on
* an Internet port below IPPORT_RESERVED (1024). This is generally not
* used, though, as the client then has to run as a user authorized for
* privileged, which is dangerous. It is also not generally needed.
*
*
* @return
*
* true
if the binding succeeds,
* false
otherwise.
*
* @throws RpcException If an exception occurs, or if no binding succeeds.
*/
private Channel bindToPrivilegedPort() throws RpcException {
System.out.println("Attempting to use privileged port.");
for (int port = 1023; port > 0; --port) {
try {
ChannelPipeline pipeline = _clientBootstrap.getPipelineFactory().getPipeline();
Channel channel = _clientBootstrap.getFactory().newChannel(pipeline);
channel.getConfig().setOptions(_clientBootstrap.getOptions());
ChannelFuture bindFuture = channel.bind(new InetSocketAddress(port)).awaitUninterruptibly();
if (bindFuture.isSuccess()) {
System.out.println("Success! Bound to port " + port);
return bindFuture.getChannel();
}
} catch (Exception e) {
String msg = String.format("rpc request bind error for address: %s",
getRemoteAddress());
throw new RpcException(RpcStatus.NETWORK_ERROR, msg, e);
}
}
throw new RpcException(RpcStatus.LOCAL_BINDING_ERROR, String.format("Cannot bind a port < 1024: %s", getRemoteAddress()));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy