1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.ipc;
19
20 import static org.apache.hadoop.hbase.ipc.CallEvent.Type.CANCELLED;
21 import static org.apache.hadoop.hbase.ipc.CallEvent.Type.TIMEOUT;
22 import static org.apache.hadoop.hbase.ipc.IPCUtil.setCancelled;
23 import static org.apache.hadoop.hbase.ipc.IPCUtil.toIOE;
24
25 import com.google.protobuf.RpcCallback;
26
27 import io.netty.bootstrap.Bootstrap;
28 import io.netty.buffer.ByteBuf;
29 import io.netty.buffer.ByteBufOutputStream;
30 import io.netty.buffer.Unpooled;
31 import io.netty.channel.Channel;
32 import io.netty.channel.ChannelFuture;
33 import io.netty.channel.ChannelFutureListener;
34 import io.netty.channel.ChannelHandler;
35 import io.netty.channel.ChannelOption;
36 import io.netty.channel.ChannelPipeline;
37 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
38 import io.netty.handler.timeout.IdleStateHandler;
39 import io.netty.util.ReferenceCountUtil;
40 import io.netty.util.concurrent.Future;
41 import io.netty.util.concurrent.FutureListener;
42 import io.netty.util.concurrent.Promise;
43
44 import java.io.IOException;
45 import java.util.concurrent.Executors;
46 import java.util.concurrent.ScheduledExecutorService;
47 import java.util.concurrent.ThreadLocalRandom;
48 import java.util.concurrent.TimeUnit;
49
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52 import org.apache.hadoop.hbase.classification.InterfaceAudience;
53 import org.apache.hadoop.hbase.ipc.BufferCallBeforeInitHandler.BufferCallEvent;
54 import org.apache.hadoop.hbase.ipc.HBaseRpcController.CancellationCallback;
55 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ConnectionHeader;
56 import org.apache.hadoop.hbase.security.NettyHBaseSaslRpcClientHandler;
57 import org.apache.hadoop.hbase.security.SaslChallengeDecoder;
58 import org.apache.hadoop.hbase.security.SaslUtil.QualityOfProtection;
59 import org.apache.hadoop.hbase.util.Threads;
60 import org.apache.hadoop.security.UserGroupInformation;
61
62
63
64
65
66
67
68 @InterfaceAudience.Private
69 class NettyRpcConnection extends RpcConnection {
70
71 private static final Log LOG = LogFactory.getLog(NettyRpcConnection.class);
72
73 private static final ScheduledExecutorService RELOGIN_EXECUTOR =
74 Executors.newSingleThreadScheduledExecutor(Threads.newDaemonThreadFactory("Relogin"));
75
76 private final NettyRpcClient rpcClient;
77
78 private ByteBuf connectionHeaderPreamble;
79
80 private ByteBuf connectionHeaderWithLength;
81
82 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC",
83 justification = "connect is also under lock as notifyOnCancel will call our action directly")
84 private Channel channel;
85
86 NettyRpcConnection(NettyRpcClient rpcClient, ConnectionId remoteId) throws IOException {
87 super(rpcClient.conf, AbstractRpcClient.WHEEL_TIMER, remoteId, rpcClient.clusterId,
88 rpcClient.userProvider.isHBaseSecurityEnabled(), rpcClient.codec, rpcClient.compressor);
89 this.rpcClient = rpcClient;
90 byte[] connectionHeaderPreamble = getConnectionHeaderPreamble();
91 this.connectionHeaderPreamble =
92 Unpooled.directBuffer(connectionHeaderPreamble.length).writeBytes(connectionHeaderPreamble);
93 ConnectionHeader header = getConnectionHeader();
94 this.connectionHeaderWithLength = Unpooled.directBuffer(4 + header.getSerializedSize());
95 this.connectionHeaderWithLength.writeInt(header.getSerializedSize());
96 header.writeTo(new ByteBufOutputStream(this.connectionHeaderWithLength));
97 }
98
99 @Override
100 protected synchronized void callTimeout(Call call) {
101 if (channel != null) {
102 channel.pipeline().fireUserEventTriggered(new CallEvent(TIMEOUT, call));
103 }
104 }
105
106 @Override
107 public synchronized boolean isActive() {
108 return channel != null;
109 }
110
111 private void shutdown0() {
112 if (channel != null) {
113 channel.close();
114 channel = null;
115 }
116 }
117
118 @Override
119 public synchronized void shutdown() {
120 shutdown0();
121 }
122
123 @Override
124 public synchronized void cleanupConnection() {
125 if (connectionHeaderPreamble != null) {
126 ReferenceCountUtil.safeRelease(connectionHeaderPreamble);
127 }
128 if (connectionHeaderWithLength != null) {
129 ReferenceCountUtil.safeRelease(connectionHeaderWithLength);
130 }
131 }
132
133 private void established(Channel ch) {
134 ch.write(connectionHeaderWithLength.retainedDuplicate());
135 ChannelPipeline p = ch.pipeline();
136 String addBeforeHandler = p.context(BufferCallBeforeInitHandler.class).name();
137 p.addBefore(addBeforeHandler, null,
138 new IdleStateHandler(0, rpcClient.minIdleTimeBeforeClose, 0, TimeUnit.MILLISECONDS));
139 p.addBefore(addBeforeHandler, null, new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4));
140 p.addBefore(addBeforeHandler, null,
141 new NettyRpcDuplexHandler(this, rpcClient.cellBlockBuilder, codec, compressor));
142 p.fireUserEventTriggered(BufferCallEvent.success());
143 }
144
145 private boolean reloginInProgress;
146
147 private void scheduleRelogin(Throwable error) {
148 if (error instanceof FallbackDisallowedException) {
149 return;
150 }
151 synchronized (this) {
152 if (reloginInProgress) {
153 return;
154 }
155 reloginInProgress = true;
156 RELOGIN_EXECUTOR.schedule(new Runnable() {
157
158 @Override
159 public void run() {
160 try {
161 if (shouldAuthenticateOverKrb()) {
162 relogin();
163 }
164 } catch (IOException e) {
165 LOG.warn("relogin failed", e);
166 }
167 synchronized (this) {
168 reloginInProgress = false;
169 }
170 }
171 }, ThreadLocalRandom.current().nextInt(reloginMaxBackoff), TimeUnit.MILLISECONDS);
172 }
173 }
174
175 private void failInit(Channel ch, IOException e) {
176 synchronized (this) {
177
178 ch.pipeline().fireUserEventTriggered(BufferCallEvent.fail(e));
179 shutdown0();
180 return;
181 }
182 }
183
184 private void saslNegotiate(final Channel ch) {
185 UserGroupInformation ticket = getUGI();
186 if (ticket == null) {
187 failInit(ch, new FatalConnectionException("ticket/user is null"));
188 return;
189 }
190 Promise<Boolean> saslPromise = ch.eventLoop().newPromise();
191 ChannelHandler saslHandler;
192 try {
193 saslHandler = new NettyHBaseSaslRpcClientHandler(saslPromise, ticket, authMethod, token,
194 serverPrincipal, rpcClient.fallbackAllowed, this.rpcClient.conf.get(
195 "hbase.rpc.protection", QualityOfProtection.AUTHENTICATION.name().toLowerCase()));
196 } catch (IOException e) {
197 failInit(ch, e);
198 return;
199 }
200 ch.pipeline().addFirst(new SaslChallengeDecoder(), saslHandler);
201 saslPromise.addListener(new FutureListener<Boolean>() {
202
203 @Override
204 public void operationComplete(Future<Boolean> future) throws Exception {
205 if (future.isSuccess()) {
206 ChannelPipeline p = ch.pipeline();
207 p.remove(SaslChallengeDecoder.class);
208 p.remove(NettyHBaseSaslRpcClientHandler.class);
209 established(ch);
210 } else {
211 final Throwable error = future.cause();
212 scheduleRelogin(error);
213 failInit(ch, toIOE(error));
214 }
215 }
216 });
217 }
218
219 private void connect() {
220 if (LOG.isDebugEnabled()) {
221 LOG.debug("Connecting to " + remoteId.address);
222 }
223
224 this.channel = new Bootstrap().group(rpcClient.group).channel(rpcClient.channelClass)
225 .option(ChannelOption.TCP_NODELAY, rpcClient.isTcpNoDelay())
226 .option(ChannelOption.SO_KEEPALIVE, rpcClient.tcpKeepAlive)
227 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, rpcClient.connectTO)
228 .handler(new BufferCallBeforeInitHandler()).localAddress(rpcClient.localAddr)
229 .remoteAddress(remoteId.address).connect().addListener(new ChannelFutureListener() {
230
231 @Override
232 public void operationComplete(ChannelFuture future) throws Exception {
233 Channel ch = future.channel();
234 if (!future.isSuccess()) {
235 failInit(ch, toIOE(future.cause()));
236 rpcClient.failedServers.addToFailedServers(remoteId.address, future.cause());
237 return;
238 }
239 ch.writeAndFlush(connectionHeaderPreamble.retainedDuplicate());
240 if (useSasl) {
241 saslNegotiate(ch);
242 } else {
243 established(ch);
244 }
245 }
246 }).channel();
247 }
248
249 private void write(Channel ch, final Call call) {
250 ch.writeAndFlush(call).addListener(new ChannelFutureListener() {
251
252 @Override
253 public void operationComplete(ChannelFuture future) throws Exception {
254
255
256
257 if (!future.isSuccess()) {
258 call.setException(toIOE(future.cause()));
259 }
260 }
261 });
262 }
263
264 @Override
265 public synchronized void sendRequest(final Call call, HBaseRpcController hrc) throws IOException {
266 if (reloginInProgress) {
267 throw new IOException("Can not send request because relogin is in progress.");
268 }
269 hrc.notifyOnCancel(new RpcCallback<Object>() {
270
271 @Override
272 public void run(Object parameter) {
273 setCancelled(call);
274 synchronized (this) {
275 if (channel != null) {
276 channel.pipeline().fireUserEventTriggered(new CallEvent(CANCELLED, call));
277 }
278 }
279 }
280 }, new CancellationCallback() {
281
282 @Override
283 public void run(boolean cancelled) throws IOException {
284 if (cancelled) {
285 setCancelled(call);
286 } else {
287 if (channel == null) {
288 connect();
289 }
290 scheduleTimeoutTask(call);
291 final Channel ch = channel;
292
293
294
295
296
297
298
299
300
301
302
303 if (ch.eventLoop().inEventLoop()) {
304 write(ch, call);
305 } else {
306 ch.eventLoop().execute(new Runnable() {
307
308 @Override
309 public void run() {
310 write(ch, call);
311 }
312 });
313 }
314 }
315 }
316 });
317 }
318 }