/*
* Copyright (c) 2009 - 2016 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this program (see the file COPYING.LIB for more
* details); if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.dcache.xdr;
import org.dcache.utils.net.InetSocketAddresses;
import org.dcache.xdr.gss.GssProtocolFilter;
import org.dcache.xdr.gss.GssSessionManager;
import org.dcache.xdr.portmap.GenericPortmapClient;
import org.dcache.xdr.portmap.OncPortmapClient;
import org.dcache.xdr.portmap.OncRpcPortmap;
import org.glassfish.grizzly.CloseType;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.ConnectionProbe;
import org.glassfish.grizzly.GrizzlyFuture;
import org.glassfish.grizzly.PortRange;
import org.glassfish.grizzly.SocketBinder;
import org.glassfish.grizzly.Transport;
import org.glassfish.grizzly.filterchain.FilterChain;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.jmxbase.GrizzlyJmxManager;
import org.glassfish.grizzly.nio.NIOTransport;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder;
import org.glassfish.grizzly.nio.transport.UDPNIOTransport;
import org.glassfish.grizzly.nio.transport.UDPNIOTransportBuilder;
import org.glassfish.grizzly.strategies.SameThreadIOStrategy;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.base.Throwables.propagateIfPossible;
import java.net.SocketAddress;
import static org.dcache.xdr.GrizzlyUtils.getSelectorPoolCfg;
import static org.dcache.xdr.GrizzlyUtils.rpcMessageReceiverFor;
import static org.dcache.xdr.GrizzlyUtils.transportFor;
public class OncRpcSvc {
private final static Logger _log = LoggerFactory.getLogger(OncRpcSvc.class);
private final int _backlog;
private final boolean _publish;
private final PortRange _portRange;
private final String _bindAddress;
private final boolean _isClient;
private final List<NIOTransport> _transports = new ArrayList<>();
private final Set<Connection<InetSocketAddress>> _boundConnections =
new HashSet<>();
private final ExecutorService _requestExecutor;
private final ReplyQueue _replyQueue = new ReplyQueue();
private final boolean _withSubjectPropagation;
/**
* Handle RPCSEC_GSS
*/
private final GssSessionManager _gssSessionManager;
/**
* mapping of registered programs.
*/
private final Map<OncRpcProgram, RpcDispatchable> _programs =
new ConcurrentHashMap<>();
/**
* Create new RPC service with defined configuration.
* @param builder to build this service
*/
OncRpcSvc(OncRpcSvcBuilder builder) {
_publish = builder.isAutoPublish();
final int protocol = builder.getProtocol();
if ((protocol & (IpProtocolType.TCP | IpProtocolType.UDP)) == 0) {
throw new IllegalArgumentException("TCP or UDP protocol have to be defined");
}
IoStrategy ioStrategy = builder.getIoStrategy();
String serviceName = builder.getServiceName();
ThreadPoolConfig selectorPoolConfig = getSelectorPoolCfg(ioStrategy,
serviceName,
builder.getSelectorThreadPoolSize());
if ((protocol & IpProtocolType.TCP) != 0) {
final TCPNIOTransport tcpTransport = TCPNIOTransportBuilder
.newInstance()
.setReuseAddress(true)
.setIOStrategy(SameThreadIOStrategy.getInstance())
.setSelectorThreadPoolConfig(selectorPoolConfig)
.setSelectorRunnersCount(selectorPoolConfig.getMaxPoolSize())
.build();
_transports.add(tcpTransport);
}
if ((protocol & IpProtocolType.UDP) != 0) {
final UDPNIOTransport udpTransport = UDPNIOTransportBuilder
.newInstance()
.setReuseAddress(true)
.setIOStrategy(SameThreadIOStrategy.getInstance())
.setSelectorThreadPoolConfig(selectorPoolConfig)
.setSelectorRunnersCount(selectorPoolConfig.getMaxPoolSize())
.build();
_transports.add(udpTransport);
}
_isClient = builder.isClient();
_portRange = builder.getMinPort() > 0 ?
new PortRange(builder.getMinPort(), builder.getMaxPort()) : null;
_backlog = builder.getBacklog();
_bindAddress = builder.getBindAddress();
if (builder.isWithJMX()) {
final GrizzlyJmxManager jmxManager = GrizzlyJmxManager.instance();
for (Transport t : _transports) {
jmxManager.registerAtRoot(t.getMonitoringConfig().createManagementObject(), t.getName() + "-" + _portRange);
}
}
_requestExecutor = builder.getWorkerThreadExecutorService();
_gssSessionManager = builder.getGssSessionManager();
_programs.putAll(builder.getRpcServices());
_withSubjectPropagation = builder.getSubjectPropagation();
}
/**
* Register a new PRC service. Existing registration will be overwritten.
*
* @param prog program number
* @param handler RPC requests handler.
*/
public void register(OncRpcProgram prog, RpcDispatchable handler) {
_log.info("Registering new program {} : {}", prog, handler);
_programs.put(prog, handler);
}
/**
* Unregister program.
*
* @param prog
*/
public void unregister(OncRpcProgram prog) {
_log.info("Unregistering program {}", prog);
_programs.remove(prog);
}
/**
* Add programs to existing services.
* @param services
* @deprecated use {@link OncRpcSvcBuilder#withRpcService} instead.
*/
public void setPrograms(Map<OncRpcProgram, RpcDispatchable> services) {
_programs.putAll(services);
}
/**
* Register services in portmap.
*
* @throws IOException
* @throws UnknownHostException
*/
private void publishToPortmap(Connection<InetSocketAddress> connection, Set<OncRpcProgram> programs) throws IOException {
OncRpcClient rpcClient = new OncRpcClient(InetAddress.getByName(null),
IpProtocolType.UDP, OncRpcPortmap.PORTMAP_PORT);
XdrTransport transport = rpcClient.connect();
try {
OncPortmapClient portmapClient = new GenericPortmapClient(transport);
Set<String> netids = new HashSet<>();
String username = System.getProperty("user.name");
Transport t = connection.getTransport();
String uaddr = InetSocketAddresses.uaddrOf(connection.getLocalAddress());
String netidBase;
if (t instanceof TCPNIOTransport) {
netidBase = "tcp";
} else if (t instanceof UDPNIOTransport) {
netidBase = "udp";
} else {
// must never happens
throw new RuntimeException("Unsupported transport type: " + t.getClass().getCanonicalName());
}
InetAddress localAddress = connection.getLocalAddress().getAddress();
if (localAddress instanceof Inet6Address) {
netids.add(netidBase + "6");
if (((Inet6Address)localAddress).isIPv4CompatibleAddress()) {
netids.add(netidBase);
}
} else {
netids.add(netidBase);
}
for (OncRpcProgram program : programs) {
for (String netid : netids) {
try {
portmapClient.setPort(program.getNumber(), program.getVersion(),
netid, uaddr, username);
} catch (OncRpcException | TimeoutException e) {
_log.warn("Failed to register program: {}", e.getMessage());
}
}
}
} catch (RpcProgUnavailable e) {
_log.warn("Failed to register at portmap: {}", e.getMessage());
} finally {
rpcClient.close();
}
}
/**
* UnRegister services in portmap.
*
* @throws IOException
* @throws UnknownHostException
*/
private void clearPortmap(Set<OncRpcProgram> programs) throws IOException {
OncRpcClient rpcClient = new OncRpcClient(InetAddress.getByName(null),
IpProtocolType.UDP, OncRpcPortmap.PORTMAP_PORT);
XdrTransport transport = rpcClient.connect();
try {
OncPortmapClient portmapClient = new GenericPortmapClient(transport);
String username = System.getProperty("user.name");
for (OncRpcProgram program : programs) {
try {
portmapClient.unsetPort(program.getNumber(),
program.getVersion(), username);
} catch (OncRpcException | TimeoutException e) {
_log.info("Failed to unregister program: {}", e.getMessage());
}
}
} catch (RpcProgUnavailable e) {
_log.info("portmap service not available");
} finally {
rpcClient.close();
}
}
public void start() throws IOException {
if(!_isClient && _publish) {
clearPortmap(_programs.keySet());
}
for (Transport t : _transports) {
FilterChainBuilder filterChain = FilterChainBuilder.stateless();
filterChain.add(new TransportFilter());
filterChain.add(rpcMessageReceiverFor(t));
filterChain.add(new RpcProtocolFilter(_replyQueue));
// use GSS if configures
if (_gssSessionManager != null) {
filterChain.add(new GssProtocolFilter(_gssSessionManager));
}
filterChain.add(new RpcDispatcher(_requestExecutor, _programs, _withSubjectPropagation));
final FilterChain filters = filterChain.build();
t.setProcessor(filters);
t.getConnectionMonitoringConfig().addProbes(new ConnectionProbe.Adapter() {
@Override
public void onCloseEvent(Connection connection) {
if (connection.getCloseReason().getType() == CloseType.REMOTELY) {
_replyQueue.handleDisconnect((SocketAddress)connection.getLocalAddress());
}
}
});
if(!_isClient) {
Connection<InetSocketAddress> connection = _portRange == null ?
((SocketBinder) t).bind(_bindAddress, 0, _backlog) :
((SocketBinder) t).bind(_bindAddress, _portRange, _backlog);
_boundConnections.add(connection);
if (_publish) {
publishToPortmap(connection, _programs.keySet());
}
}
t.start();
}
}
public void stop() throws IOException {
if (!_isClient && _publish) {
clearPortmap(_programs.keySet());
}
for (Transport t : _transports) {
t.shutdownNow();
}
_replyQueue.shutdown();
_requestExecutor.shutdown();
}
public void stop(long gracePeriod, TimeUnit timeUnit) throws IOException {
if (!_isClient && _publish) {
clearPortmap(_programs.keySet());
}
List<GrizzlyFuture<Transport>> transportsShuttingDown = new ArrayList<>();
for (Transport t : _transports) {
transportsShuttingDown.add(t.shutdown(gracePeriod, timeUnit));
}
for (GrizzlyFuture<Transport> transportShuttingDown : transportsShuttingDown) {
try {
transportShuttingDown.get();
} catch (InterruptedException e) {
_log.info("Waiting for graceful shut down interrupted");
} catch (ExecutionException e) {
Throwable t = getRootCause(e);
_log.warn("Exception while waiting for transport to shut down gracefully",t);
}
}
_requestExecutor.shutdown();
}
public XdrTransport connect(InetSocketAddress socketAddress) throws IOException {
return connect(socketAddress, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
public XdrTransport connect(InetSocketAddress socketAddress, long timeout, TimeUnit timeUnit) throws IOException {
// in client mode only one transport is defined
NIOTransport transport = _transports.get(0);
Future<Connection> connectFuture;
if (_portRange != null) {
InetSocketAddress localAddress = new InetSocketAddress(_portRange.getLower());
connectFuture = transport.connect(socketAddress, localAddress);
} else {
connectFuture = transport.connect(socketAddress);
}
try {
//noinspection unchecked
Connection<InetSocketAddress> connection = connectFuture.get(timeout, timeUnit);
return new GrizzlyXdrTransport(connection, _replyQueue);
} catch (ExecutionException e) {
Throwable t = getRootCause(e);
propagateIfPossible(t, IOException.class);
throw new IOException(e.toString(), e);
} catch (TimeoutException | InterruptedException e) {
throw new IOException(e.toString(), e);
}
}
/**
* Returns the address of the endpoint this service is bound to,
* or <code>null</code> if it is not bound yet.
* @param protocol
* @return a {@link InetSocketAddress} representing the local endpoint of
* this service, or <code>null</code> if it is not bound yet.
*/
public InetSocketAddress getInetSocketAddress(int protocol) {
Class< ? extends Transport> transportClass = transportFor(protocol);
for (Connection<InetSocketAddress> connection: _boundConnections) {
if(connection.getTransport().getClass() == transportClass)
return connection.getLocalAddress();
}
return null;
}
}