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 io.netty.bootstrap.Bootstrap;
21 import io.netty.buffer.ByteBuf;
22 import io.netty.buffer.ByteBufOutputStream;
23 import io.netty.channel.Channel;
24 import io.netty.channel.ChannelFuture;
25 import io.netty.channel.ChannelFutureListener;
26 import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
27 import io.netty.util.Timeout;
28 import io.netty.util.TimerTask;
29 import io.netty.util.concurrent.GenericFutureListener;
30 import io.netty.util.concurrent.Promise;
31
32 import java.io.IOException;
33 import java.net.ConnectException;
34 import java.net.InetSocketAddress;
35 import java.nio.ByteBuffer;
36 import java.security.PrivilegedExceptionAction;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Random;
43 import java.util.concurrent.TimeUnit;
44
45 import javax.security.sasl.SaslException;
46
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49 import org.apache.hadoop.hbase.HConstants;
50 import org.apache.hadoop.hbase.classification.InterfaceAudience;
51 import org.apache.hadoop.hbase.client.MetricsConnection;
52 import org.apache.hadoop.hbase.exceptions.ConnectionClosingException;
53 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
54 import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
55 import org.apache.hadoop.hbase.protobuf.generated.RPCProtos;
56 import org.apache.hadoop.hbase.protobuf.generated.TracingProtos;
57 import org.apache.hadoop.hbase.security.AuthMethod;
58 import org.apache.hadoop.hbase.security.SaslClientHandler;
59 import org.apache.hadoop.hbase.security.SaslUtil;
60 import org.apache.hadoop.hbase.security.SecurityInfo;
61 import org.apache.hadoop.hbase.security.User;
62 import org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector;
63 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
64 import org.apache.hadoop.io.Text;
65 import org.apache.hadoop.ipc.RemoteException;
66 import org.apache.hadoop.security.SecurityUtil;
67 import org.apache.hadoop.security.UserGroupInformation;
68 import org.apache.hadoop.security.token.Token;
69 import org.apache.hadoop.security.token.TokenIdentifier;
70 import org.apache.hadoop.security.token.TokenSelector;
71 import org.apache.htrace.Span;
72 import org.apache.htrace.Trace;
73
74 import com.google.protobuf.Descriptors;
75 import com.google.protobuf.Message;
76 import com.google.protobuf.RpcCallback;
77
78
79
80
81 @InterfaceAudience.Private
82 public class AsyncRpcChannel {
83 private static final Log LOG = LogFactory.getLog(AsyncRpcChannel.class.getName());
84
85 private static final int MAX_SASL_RETRIES = 5;
86
87 protected final static Map<AuthenticationProtos.TokenIdentifier.Kind, TokenSelector<? extends
88 TokenIdentifier>> tokenHandlers = new HashMap<>();
89
90 static {
91 tokenHandlers.put(AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN,
92 new AuthenticationTokenSelector());
93 }
94
95 final AsyncRpcClient client;
96
97
98
99 private Channel channel;
100
101 String name;
102 final User ticket;
103 final String serviceName;
104 final InetSocketAddress address;
105
106 private int failureCounter = 0;
107
108 boolean useSasl;
109 AuthMethod authMethod;
110 private int reloginMaxBackoff;
111 private Token<? extends TokenIdentifier> token;
112 private String serverPrincipal;
113
114
115
116 private final Map<Integer, AsyncCall> pendingCalls = new HashMap<Integer, AsyncCall>();
117 private boolean connected = false;
118 private boolean closed = false;
119
120 private Timeout cleanupTimer;
121
122 private final TimerTask timeoutTask = new TimerTask() {
123 @Override
124 public void run(Timeout timeout) throws Exception {
125 cleanupCalls();
126 }
127 };
128
129
130
131
132
133
134
135
136
137
138 public AsyncRpcChannel(Bootstrap bootstrap, final AsyncRpcClient client, User ticket, String
139 serviceName, InetSocketAddress address) {
140 this.client = client;
141
142 this.ticket = ticket;
143 this.serviceName = serviceName;
144 this.address = address;
145
146 this.channel = connect(bootstrap).channel();
147
148 name = ("IPC Client (" + channel.hashCode() + ") to " +
149 address.toString() +
150 ((ticket == null) ?
151 " from unknown user" :
152 (" from " + ticket.getName())));
153 }
154
155
156
157
158
159
160
161 private ChannelFuture connect(final Bootstrap bootstrap) {
162 return bootstrap.remoteAddress(address).connect()
163 .addListener(new GenericFutureListener<ChannelFuture>() {
164 @Override
165 public void operationComplete(final ChannelFuture f) throws Exception {
166 if (!f.isSuccess()) {
167 retryOrClose(bootstrap, failureCounter++, client.failureSleep, f.cause());
168 return;
169 }
170 channel = f.channel();
171
172 setupAuthorization();
173
174 ByteBuf b = channel.alloc().directBuffer(6);
175 createPreamble(b, authMethod);
176 channel.writeAndFlush(b).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
177 if (useSasl) {
178 UserGroupInformation ticket = AsyncRpcChannel.this.ticket.getUGI();
179 if (authMethod == AuthMethod.KERBEROS) {
180 if (ticket != null && ticket.getRealUser() != null) {
181 ticket = ticket.getRealUser();
182 }
183 }
184 SaslClientHandler saslHandler;
185 if (ticket == null) {
186 throw new FatalConnectionException("ticket/user is null");
187 }
188 final UserGroupInformation realTicket = ticket;
189 saslHandler = ticket.doAs(new PrivilegedExceptionAction<SaslClientHandler>() {
190 @Override
191 public SaslClientHandler run() throws IOException {
192 return getSaslHandler(realTicket, bootstrap);
193 }
194 });
195 if (saslHandler != null) {
196
197 channel.pipeline().addFirst(saslHandler);
198 } else {
199
200 authMethod = AuthMethod.SIMPLE;
201 useSasl = false;
202 }
203 } else {
204 startHBaseConnection(f.channel());
205 }
206 }
207 });
208 }
209
210
211
212
213
214
215 private void startHBaseConnection(Channel ch) {
216 ch.pipeline()
217 .addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
218 ch.pipeline().addLast(new AsyncServerResponseHandler(this));
219 try {
220 writeChannelHeader(ch).addListener(new GenericFutureListener<ChannelFuture>() {
221 @Override
222 public void operationComplete(ChannelFuture future) throws Exception {
223 if (!future.isSuccess()) {
224 close(future.cause());
225 return;
226 }
227 List<AsyncCall> callsToWrite;
228 synchronized (pendingCalls) {
229 connected = true;
230 callsToWrite = new ArrayList<AsyncCall>(pendingCalls.values());
231 }
232 for (AsyncCall call : callsToWrite) {
233 writeRequest(call);
234 }
235 }
236 });
237 } catch (IOException e) {
238 close(e);
239 }
240 }
241
242
243
244
245
246
247
248 private SaslClientHandler getSaslHandler(final UserGroupInformation realTicket,
249 final Bootstrap bootstrap) throws IOException {
250 return new SaslClientHandler(realTicket, authMethod, token, serverPrincipal,
251 client.fallbackAllowed, client.conf.get("hbase.rpc.protection",
252 SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase()),
253 new SaslClientHandler.SaslExceptionHandler() {
254 @Override
255 public void handle(int retryCount, Random random, Throwable cause) {
256 try {
257
258 handleSaslConnectionFailure(retryCount, cause, realTicket);
259
260 retryOrClose(bootstrap, failureCounter++, random.nextInt(reloginMaxBackoff) + 1,
261 cause);
262 } catch (IOException | InterruptedException e) {
263 close(e);
264 }
265 }
266 }, new SaslClientHandler.SaslSuccessfulConnectHandler() {
267 @Override
268 public void onSuccess(Channel channel) {
269 startHBaseConnection(channel);
270 }
271 });
272 }
273
274
275
276
277
278
279
280
281 private void retryOrClose(final Bootstrap bootstrap, int failureCount,
282 long timeout, Throwable e) {
283 if (failureCount < client.maxRetries) {
284 client.newTimeout(new TimerTask() {
285 @Override
286 public void run(Timeout timeout) throws Exception {
287 connect(bootstrap);
288 }
289 }, timeout, TimeUnit.MILLISECONDS);
290 } else {
291 client.failedServers.addToFailedServers(address);
292 close(e);
293 }
294 }
295
296
297
298
299
300
301
302
303 public Promise<Message> callMethod(final Descriptors.MethodDescriptor method,
304 final PayloadCarryingRpcController controller, final Message request,
305 final Message responsePrototype, MetricsConnection.CallStats callStats) {
306 final AsyncCall call =
307 new AsyncCall(channel.eventLoop(), client.callIdCnt.getAndIncrement(), method, request,
308 controller, responsePrototype, callStats);
309 controller.notifyOnCancel(new RpcCallback<Object>() {
310 @Override
311 public void run(Object parameter) {
312
313 synchronized (pendingCalls) {
314 pendingCalls.remove(call.id);
315 }
316 }
317 });
318
319 if (controller.isCanceled()) {
320
321 call.cancel(true);
322 return call;
323 }
324
325 synchronized (pendingCalls) {
326 if (closed) {
327 Promise<Message> promise = channel.eventLoop().newPromise();
328 promise.setFailure(new ConnectException());
329 return promise;
330 }
331 pendingCalls.put(call.id, call);
332
333 if (cleanupTimer == null && call.getRpcTimeout() > 0) {
334 cleanupTimer =
335 client.newTimeout(timeoutTask, call.getRpcTimeout(),
336 TimeUnit.MILLISECONDS);
337 }
338 if (!connected) {
339 return call;
340 }
341 }
342 writeRequest(call);
343 return call;
344 }
345
346 AsyncCall removePendingCall(int id) {
347 synchronized (pendingCalls) {
348 return pendingCalls.remove(id);
349 }
350 }
351
352
353
354
355
356
357
358
359 private ChannelFuture writeChannelHeader(Channel channel) throws IOException {
360 RPCProtos.ConnectionHeader.Builder headerBuilder =
361 RPCProtos.ConnectionHeader.newBuilder().setServiceName(serviceName);
362
363 RPCProtos.UserInformation userInfoPB = buildUserInfo(ticket.getUGI(), authMethod);
364 if (userInfoPB != null) {
365 headerBuilder.setUserInfo(userInfoPB);
366 }
367
368 if (client.codec != null) {
369 headerBuilder.setCellBlockCodecClass(client.codec.getClass().getCanonicalName());
370 }
371 if (client.compressor != null) {
372 headerBuilder.setCellBlockCompressorClass(client.compressor.getClass().getCanonicalName());
373 }
374
375 headerBuilder.setVersionInfo(ProtobufUtil.getVersionInfo());
376 RPCProtos.ConnectionHeader header = headerBuilder.build();
377
378
379 int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(header);
380
381 ByteBuf b = channel.alloc().directBuffer(totalSize);
382
383 b.writeInt(header.getSerializedSize());
384 b.writeBytes(header.toByteArray());
385
386 return channel.writeAndFlush(b);
387 }
388
389
390
391
392
393
394 private void writeRequest(final AsyncCall call) {
395 try {
396 final RPCProtos.RequestHeader.Builder requestHeaderBuilder = RPCProtos.RequestHeader
397 .newBuilder();
398 requestHeaderBuilder.setCallId(call.id)
399 .setMethodName(call.method.getName()).setRequestParam(call.param != null);
400
401 if (Trace.isTracing()) {
402 Span s = Trace.currentSpan();
403 requestHeaderBuilder.setTraceInfo(TracingProtos.RPCTInfo.newBuilder().
404 setParentId(s.getSpanId()).setTraceId(s.getTraceId()));
405 }
406
407 ByteBuffer cellBlock = client.buildCellBlock(call.controller.cellScanner());
408 if (cellBlock != null) {
409 final RPCProtos.CellBlockMeta.Builder cellBlockBuilder = RPCProtos.CellBlockMeta
410 .newBuilder();
411 cellBlockBuilder.setLength(cellBlock.limit());
412 requestHeaderBuilder.setCellBlockMeta(cellBlockBuilder.build());
413 }
414
415 if (call.controller.getPriority() != 0) {
416 requestHeaderBuilder.setPriority(call.controller.getPriority());
417 }
418
419 RPCProtos.RequestHeader rh = requestHeaderBuilder.build();
420
421 int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(rh, call.param);
422 if (cellBlock != null) {
423 totalSize += cellBlock.remaining();
424 }
425
426 ByteBuf b = channel.alloc().directBuffer(4 + totalSize);
427 try(ByteBufOutputStream out = new ByteBufOutputStream(b)) {
428 call.callStats.setRequestSizeBytes(IPCUtil.write(out, rh, call.param, cellBlock));
429 }
430
431 channel.writeAndFlush(b).addListener(new CallWriteListener(this, call.id));
432 } catch (IOException e) {
433 close(e);
434 }
435 }
436
437
438
439
440
441
442 private void setupAuthorization() throws IOException {
443 SecurityInfo securityInfo = SecurityInfo.getInfo(serviceName);
444 this.useSasl = client.userProvider.isHBaseSecurityEnabled();
445
446 this.token = null;
447 if (useSasl && securityInfo != null) {
448 AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind();
449 if (tokenKind != null) {
450 TokenSelector<? extends TokenIdentifier> tokenSelector = tokenHandlers.get(tokenKind);
451 if (tokenSelector != null) {
452 token = tokenSelector
453 .selectToken(new Text(client.clusterId), ticket.getUGI().getTokens());
454 } else if (LOG.isDebugEnabled()) {
455 LOG.debug("No token selector found for type " + tokenKind);
456 }
457 }
458 String serverKey = securityInfo.getServerPrincipal();
459 if (serverKey == null) {
460 throw new IOException("Can't obtain server Kerberos config key from SecurityInfo");
461 }
462 this.serverPrincipal = SecurityUtil.getServerPrincipal(client.conf.get(serverKey),
463 address.getAddress().getCanonicalHostName().toLowerCase());
464 if (LOG.isDebugEnabled()) {
465 LOG.debug("RPC Server Kerberos principal name for service=" + serviceName + " is "
466 + serverPrincipal);
467 }
468 }
469
470 if (!useSasl) {
471 authMethod = AuthMethod.SIMPLE;
472 } else if (token != null) {
473 authMethod = AuthMethod.DIGEST;
474 } else {
475 authMethod = AuthMethod.KERBEROS;
476 }
477
478 if (LOG.isDebugEnabled()) {
479 LOG.debug("Use " + authMethod + " authentication for service " + serviceName +
480 ", sasl=" + useSasl);
481 }
482 reloginMaxBackoff = client.conf.getInt("hbase.security.relogin.maxbackoff", 5000);
483 }
484
485
486
487
488
489
490
491
492 private RPCProtos.UserInformation buildUserInfo(UserGroupInformation ugi, AuthMethod authMethod) {
493 if (ugi == null || authMethod == AuthMethod.DIGEST) {
494
495 return null;
496 }
497 RPCProtos.UserInformation.Builder userInfoPB = RPCProtos.UserInformation.newBuilder();
498 if (authMethod == AuthMethod.KERBEROS) {
499
500 userInfoPB.setEffectiveUser(ugi.getUserName());
501 } else if (authMethod == AuthMethod.SIMPLE) {
502
503 userInfoPB.setEffectiveUser(ugi.getUserName());
504 if (ugi.getRealUser() != null) {
505 userInfoPB.setRealUser(ugi.getRealUser().getUserName());
506 }
507 }
508 return userInfoPB.build();
509 }
510
511
512
513
514
515
516
517 private void createPreamble(ByteBuf byteBuf, AuthMethod authMethod) {
518 byteBuf.writeBytes(HConstants.RPC_HEADER);
519 byteBuf.writeByte(HConstants.RPC_CURRENT_VERSION);
520 byteBuf.writeByte(authMethod.code);
521 }
522
523
524
525
526
527
528 public void close(final Throwable e) {
529 client.removeConnection(this);
530
531
532 channel.eventLoop().execute(new Runnable() {
533 @Override
534 public void run() {
535 List<AsyncCall> toCleanup;
536 synchronized (pendingCalls) {
537 if (closed) {
538 return;
539 }
540 closed = true;
541 toCleanup = new ArrayList<AsyncCall>(pendingCalls.values());
542 pendingCalls.clear();
543 }
544 IOException closeException = null;
545 if (e != null) {
546 if (e instanceof IOException) {
547 closeException = (IOException) e;
548 } else {
549 closeException = new IOException(e);
550 }
551 }
552
553 if (LOG.isDebugEnabled() && closeException != null) {
554 LOG.debug(name + ": closing ipc connection to " + address, closeException);
555 }
556 if (cleanupTimer != null) {
557 cleanupTimer.cancel();
558 cleanupTimer = null;
559 }
560 for (AsyncCall call : toCleanup) {
561 call.setFailed(closeException != null ? closeException : new ConnectionClosingException(
562 "Call id=" + call.id + " on server " + address + " aborted: connection is closing"));
563 }
564 channel.disconnect().addListener(ChannelFutureListener.CLOSE);
565 if (LOG.isDebugEnabled()) {
566 LOG.debug(name + ": closed");
567 }
568 }
569 });
570 }
571
572
573
574
575 private void cleanupCalls() {
576 List<AsyncCall> toCleanup = new ArrayList<AsyncCall>();
577 long currentTime = EnvironmentEdgeManager.currentTime();
578 long nextCleanupTaskDelay = -1L;
579 synchronized (pendingCalls) {
580 for (Iterator<AsyncCall> iter = pendingCalls.values().iterator(); iter.hasNext();) {
581 AsyncCall call = iter.next();
582 long timeout = call.getRpcTimeout();
583 if (timeout > 0) {
584 if (currentTime - call.getStartTime() >= timeout) {
585 iter.remove();
586 toCleanup.add(call);
587 } else {
588 if (nextCleanupTaskDelay < 0 || timeout < nextCleanupTaskDelay) {
589 nextCleanupTaskDelay = timeout;
590 }
591 }
592 }
593 }
594 if (nextCleanupTaskDelay > 0) {
595 cleanupTimer =
596 client.newTimeout(timeoutTask, nextCleanupTaskDelay,
597 TimeUnit.MILLISECONDS);
598 } else {
599 cleanupTimer = null;
600 }
601 }
602 for (AsyncCall call : toCleanup) {
603 call.setFailed(new CallTimeoutException("Call id=" + call.id + ", waitTime="
604 + (currentTime - call.getStartTime()) + ", rpcTimeout=" + call.getRpcTimeout()));
605 }
606 }
607
608
609
610
611
612
613 public boolean isAlive() {
614 return channel.isOpen();
615 }
616
617
618
619
620
621
622
623 private synchronized boolean shouldAuthenticateOverKrb() throws IOException {
624 UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
625 UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
626 UserGroupInformation realUser = currentUser.getRealUser();
627 return authMethod == AuthMethod.KERBEROS &&
628 loginUser != null &&
629
630 loginUser.hasKerberosCredentials() &&
631
632
633 (loginUser.equals(currentUser) || loginUser.equals(realUser));
634 }
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660 private void handleSaslConnectionFailure(final int currRetries, final Throwable ex,
661 final UserGroupInformation user) throws IOException, InterruptedException {
662 user.doAs(new PrivilegedExceptionAction<Void>() {
663 public Void run() throws IOException, InterruptedException {
664 if (shouldAuthenticateOverKrb()) {
665 if (currRetries < MAX_SASL_RETRIES) {
666 LOG.debug("Exception encountered while connecting to the server : " + ex);
667
668 if (UserGroupInformation.isLoginKeytabBased()) {
669 UserGroupInformation.getLoginUser().reloginFromKeytab();
670 } else {
671 UserGroupInformation.getLoginUser().reloginFromTicketCache();
672 }
673
674
675 return null;
676 } else {
677 String msg = "Couldn't setup connection for " +
678 UserGroupInformation.getLoginUser().getUserName() +
679 " to " + serverPrincipal;
680 LOG.warn(msg);
681 throw (IOException) new IOException(msg).initCause(ex);
682 }
683 } else {
684 LOG.warn("Exception encountered while connecting to " +
685 "the server : " + ex);
686 }
687 if (ex instanceof RemoteException) {
688 throw (RemoteException) ex;
689 }
690 if (ex instanceof SaslException) {
691 String msg = "SASL authentication failed." +
692 " The most likely cause is missing or invalid credentials." +
693 " Consider 'kinit'.";
694 LOG.fatal(msg, ex);
695 throw new RuntimeException(msg, ex);
696 }
697 throw new IOException(ex);
698 }
699 });
700 }
701
702 public int getConnectionHashCode() {
703 return ConnectionId.hashCode(ticket, serviceName, address);
704 }
705
706 @Override
707 public int hashCode() {
708 return getConnectionHashCode();
709 }
710
711 @Override
712 public boolean equals(Object obj) {
713 if (obj instanceof AsyncRpcChannel) {
714 AsyncRpcChannel channel = (AsyncRpcChannel) obj;
715 return channel.hashCode() == obj.hashCode();
716 }
717 return false;
718 }
719
720
721 @Override
722 public String toString() {
723 return this.address.toString() + "/" + this.serviceName + "/" + this.ticket;
724 }
725
726
727
728
729 private static final class CallWriteListener implements ChannelFutureListener {
730 private final AsyncRpcChannel rpcChannel;
731 private final int id;
732
733 public CallWriteListener(AsyncRpcChannel asyncRpcChannel, int id) {
734 this.rpcChannel = asyncRpcChannel;
735 this.id = id;
736 }
737
738 @Override
739 public void operationComplete(ChannelFuture future) throws Exception {
740 if (!future.isSuccess()) {
741 AsyncCall call = rpcChannel.removePendingCall(id);
742 if (call != null) {
743 if (future.cause() instanceof IOException) {
744 call.setFailed((IOException) future.cause());
745 } else {
746 call.setFailed(new IOException(future.cause()));
747 }
748 }
749 }
750 }
751 }
752 }