/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.alibaba.wasp.ipc;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.RetriesExhaustedException;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.util.ReflectionUtils;
import javax.net.SocketFactory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
public class WaspRPC {
protected static final Log LOG = LogFactory
.getLog("com.alibaba.wasp.ipc.WaspRPC");
/**
* Configuration key for the {@link RpcEngine} implementation to load to
* handle connection protocols. Handlers for individual protocols can be
* configured using {@code "wasp.rpc.engine." + protocol.class.name}.
*/
public static final String RPC_ENGINE_PROP = "wasp.rpc.engine";
// track what RpcEngine is used by a proxy class, for stopProxy()
private static final Map<Class, RpcEngine> PROXY_ENGINES = new HashMap<Class, RpcEngine>();
// cache of RpcEngines by protocol
private static final Map<Class, RpcEngine> PROTOCOL_ENGINES = new HashMap<Class, RpcEngine>();
/** Construct a server for a protocol implementation instance. */
public static RpcServer getServer(Class protocol, final Object instance,
final Class<?>[] ifaces, String bindAddress, int port, Configuration conf)
throws IOException {
return getProtocolEngine(protocol, conf).getServer(protocol, instance,
ifaces, bindAddress, port, conf);
}
// return the RpcEngine configured to handle a protocol
private static synchronized RpcEngine getProtocolEngine(Class protocol,
Configuration conf) {
RpcEngine engine = PROTOCOL_ENGINES.get(protocol);
if (engine == null) {
// check for a configured default engine
Class<?> defaultEngine = conf.getClass(RPC_ENGINE_PROP,
ProtobufRpcEngine.class);
// check for a per interface override
Class<?> impl = conf.getClass(RPC_ENGINE_PROP + "." + protocol.getName(),
defaultEngine);
LOG.debug("Using " + impl.getName() + " for " + protocol.getName());
engine = (RpcEngine) ReflectionUtils.newInstance(impl, conf);
if (protocol.isInterface())
PROXY_ENGINES.put(
Proxy.getProxyClass(protocol.getClassLoader(), protocol), engine);
PROTOCOL_ENGINES.put(protocol, engine);
}
return engine;
}
/**
* Construct a client-side proxy object that implements the named protocol,
* talking to a server at the named address.
*
* @param protocol
* interface
* @param clientVersion
* version we are expecting
* @param addr
* remote address
* @param conf
* configuration
* @param factory
* socket factory
* @param rpcTimeout
* timeout for each RPC
* @return proxy
* @throws java.io.IOException
* e
*/
public static VersionedProtocol getProxy(
Class<? extends VersionedProtocol> protocol, long clientVersion,
InetSocketAddress addr, Configuration conf, SocketFactory factory,
int rpcTimeout) throws IOException {
RpcEngine engine = getProtocolEngine(protocol, conf);
VersionedProtocol proxy = engine.getProxy(protocol, clientVersion, addr,
conf, factory, rpcTimeout);
return proxy;
}
/**
* Construct a client-side proxy object with the default SocketFactory
*
* @param protocol
* interface
* @param clientVersion
* version we are expecting
* @param addr
* remote address
* @param conf
* configuration
* @param rpcTimeout
* timeout for each RPC
* @return a proxy instance
* @throws java.io.IOException
* e
*/
public static VersionedProtocol getProxy(
Class<? extends VersionedProtocol> protocol, long clientVersion,
InetSocketAddress addr, Configuration conf, int rpcTimeout)
throws IOException {
return getProxy(protocol, clientVersion, addr, conf,
NetUtils.getDefaultSocketFactory(conf), rpcTimeout);
}
static long getProtocolVersion(Class<? extends VersionedProtocol> protocol)
throws NoSuchFieldException, IllegalAccessException {
Field versionField = protocol.getField("VERSION");
versionField.setAccessible(true);
return versionField.getLong(protocol);
}
/**
* @param protocol
* protocol interface
* @param clientVersion
* which client version we expect
* @param addr
* address of remote service
* @param conf
* configuration
* @param maxAttempts
* max attempts
* @param rpcTimeout
* timeout for each RPC
* @param timeout
* timeout in milliseconds
* @return proxy
* @throws java.io.IOException
* e
*/
@SuppressWarnings("unchecked")
public static VersionedProtocol waitForProxy(Class protocol,
long clientVersion, InetSocketAddress addr, Configuration conf,
int maxAttempts, int rpcTimeout, long timeout) throws IOException {
long startTime = System.currentTimeMillis();
IOException ioe;
int reconnectAttempts = 0;
while (true) {
try {
return getProxy(protocol, clientVersion, addr, conf, rpcTimeout);
} catch (SocketTimeoutException te) { // namenode is busy
LOG.info("Problem connecting to server: " + addr);
ioe = te;
} catch (IOException ioex) {
// We only handle the ConnectException.
ConnectException ce = null;
if (ioex instanceof ConnectException) {
ce = (ConnectException) ioex;
ioe = ce;
} else if (ioex.getCause() != null
&& ioex.getCause() instanceof ConnectException) {
ce = (ConnectException) ioex.getCause();
ioe = ce;
} else if (ioex.getMessage().toLowerCase()
.contains("connection refused")) {
ce = new ConnectException(ioex.getMessage());
ioe = ce;
} else {
// This is the exception we can't handle.
ioe = ioex;
}
if (ce != null) {
handleConnectionException(++reconnectAttempts, maxAttempts, protocol,
addr, ce);
}
}
// check if timed out
if (System.currentTimeMillis() - timeout >= startTime) {
throw ioe;
}
// wait for retry
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
// IGNORE
}
}
}
/**
* @param retries
* current retried times.
* @param maxAttmpts
* max attempts
* @param protocol
* protocol interface
* @param addr
* address of remote service
* @param ce
* ConnectException
* @throws org.apache.hadoop.hbase.client.RetriesExhaustedException
*/
private static void handleConnectionException(int retries, int maxAttmpts,
Class<?> protocol, InetSocketAddress addr, ConnectException ce)
throws RetriesExhaustedException {
if (maxAttmpts >= 0 && retries >= maxAttmpts) {
LOG.info("Server at " + addr + " could not be reached after "
+ maxAttmpts + " tries, giving up.");
throw new RetriesExhaustedException("Failed setting up proxy " + protocol
+ " to " + addr.toString() + " after attempts=" + maxAttmpts, ce);
}
}
public static void stopProxy(VersionedProtocol proxy) {
if (proxy != null) {
getProxyEngine(proxy).stopProxy(proxy);
}
}
// return the RpcEngine that handles a proxy object
private static synchronized RpcEngine getProxyEngine(Object proxy) {
return PROXY_ENGINES.get(proxy.getClass());
}
}