View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.ipc;
19  
20  import java.io.IOException;
21  import java.net.ConnectException;
22  import java.net.InetSocketAddress;
23  import java.net.SocketException;
24  import java.nio.ByteBuffer;
25  import java.security.PrivilegedExceptionAction;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Random;
32  import java.util.concurrent.TimeUnit;
33  
34  import javax.security.sasl.SaslException;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.hbase.HConstants;
39  import org.apache.hadoop.hbase.classification.InterfaceAudience;
40  import org.apache.hadoop.hbase.client.MetricsConnection;
41  import org.apache.hadoop.hbase.exceptions.ConnectionClosingException;
42  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
43  import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
44  import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos.TokenIdentifier.Kind;
45  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos;
46  import org.apache.hadoop.hbase.protobuf.generated.TracingProtos;
47  import org.apache.hadoop.hbase.security.AuthMethod;
48  import org.apache.hadoop.hbase.security.SaslClientHandler;
49  import org.apache.hadoop.hbase.security.SaslUtil;
50  import org.apache.hadoop.hbase.security.SecurityInfo;
51  import org.apache.hadoop.hbase.security.User;
52  import org.apache.hadoop.hbase.security.token.AuthenticationTokenSelector;
53  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
54  import org.apache.hadoop.io.Text;
55  import org.apache.hadoop.ipc.RemoteException;
56  import org.apache.hadoop.security.SecurityUtil;
57  import org.apache.hadoop.security.UserGroupInformation;
58  import org.apache.hadoop.security.token.Token;
59  import org.apache.hadoop.security.token.TokenIdentifier;
60  import org.apache.hadoop.security.token.TokenSelector;
61  import org.apache.htrace.Span;
62  import org.apache.htrace.Trace;
63  
64  import com.google.protobuf.Descriptors;
65  import com.google.protobuf.Message;
66  import com.google.protobuf.RpcCallback;
67  
68  import io.netty.bootstrap.Bootstrap;
69  import io.netty.buffer.ByteBuf;
70  import io.netty.buffer.ByteBufOutputStream;
71  import io.netty.channel.Channel;
72  import io.netty.channel.ChannelFuture;
73  import io.netty.channel.ChannelFutureListener;
74  import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
75  import io.netty.util.Timeout;
76  import io.netty.util.TimerTask;
77  import io.netty.util.concurrent.GenericFutureListener;
78  import io.netty.util.concurrent.Promise;
79  
80  /**
81   * Netty RPC channel
82   */
83  @InterfaceAudience.Private
84  public class AsyncRpcChannel {
85    private static final Log LOG = LogFactory.getLog(AsyncRpcChannel.class.getName());
86  
87    private static final int MAX_SASL_RETRIES = 5;
88  
89    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="MS_MUTABLE_COLLECTION_PKGPROTECT",
90        justification="the rest of the system which live in the different package can use")
91    protected final static Map<Kind, TokenSelector<? extends TokenIdentifier>> TOKEN_HANDDLERS
92      = new HashMap<>();
93  
94    static {
95      TOKEN_HANDDLERS.put(AuthenticationProtos.TokenIdentifier.Kind.HBASE_AUTH_TOKEN,
96        new AuthenticationTokenSelector());
97    }
98  
99    final AsyncRpcClient client;
100 
101   // Contains the channel to work with.
102   // Only exists when connected
103   private Channel channel;
104 
105   String name;
106   final User ticket;
107   final String serviceName;
108   final InetSocketAddress address;
109 
110   private int ioFailureCounter = 0;
111   private int connectFailureCounter = 0;
112 
113   boolean useSasl;
114   AuthMethod authMethod;
115   private int reloginMaxBackoff;
116   private Token<? extends TokenIdentifier> token;
117   private String serverPrincipal;
118 
119   // NOTE: closed and connected flags below are only changed when a lock on pendingCalls
120   private final Map<Integer, AsyncCall> pendingCalls = new HashMap<Integer, AsyncCall>();
121   private boolean connected = false;
122   private boolean closed = false;
123 
124   private Timeout cleanupTimer;
125 
126   private final TimerTask timeoutTask = new TimerTask() {
127     @Override
128     public void run(Timeout timeout) throws Exception {
129       cleanupCalls();
130     }
131   };
132 
133   /**
134    * Constructor for netty RPC channel
135    * @param bootstrap to construct channel on
136    * @param client to connect with
137    * @param ticket of user which uses connection
138    *               @param serviceName name of service to connect to
139    * @param address to connect to
140    */
141   public AsyncRpcChannel(Bootstrap bootstrap, final AsyncRpcClient client, User ticket,
142       String serviceName, InetSocketAddress address) {
143     this.client = client;
144 
145     this.ticket = ticket;
146     this.serviceName = serviceName;
147     this.address = address;
148 
149     this.channel = connect(bootstrap).channel();
150 
151     name = ("IPC Client (" + channel.hashCode() + ") to " + address.toString()
152         + ((ticket == null) ? " from unknown user" : (" from " + ticket.getName())));
153   }
154 
155   /**
156    * Connect to channel
157    * @param bootstrap to connect to
158    * @return future of connection
159    */
160   private ChannelFuture connect(final Bootstrap bootstrap) {
161     return bootstrap.remoteAddress(address).connect()
162         .addListener(new GenericFutureListener<ChannelFuture>() {
163           @Override
164           public void operationComplete(final ChannelFuture f) throws Exception {
165             if (!f.isSuccess()) {
166               if (f.cause() instanceof SocketException) {
167                 retryOrClose(bootstrap, connectFailureCounter++, f.cause());
168               } else {
169                 retryOrClose(bootstrap, ioFailureCounter++, f.cause());
170               }
171               return;
172             }
173             channel = f.channel();
174 
175             setupAuthorization();
176 
177             ByteBuf b = channel.alloc().directBuffer(6);
178             createPreamble(b, authMethod);
179             channel.writeAndFlush(b).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
180             if (useSasl) {
181               UserGroupInformation ticket = AsyncRpcChannel.this.ticket.getUGI();
182               if (authMethod == AuthMethod.KERBEROS) {
183                 if (ticket != null && ticket.getRealUser() != null) {
184                   ticket = ticket.getRealUser();
185                 }
186               }
187               SaslClientHandler saslHandler;
188               if (ticket == null) {
189                 throw new FatalConnectionException("ticket/user is null");
190               }
191               final UserGroupInformation realTicket = ticket;
192               saslHandler = ticket.doAs(new PrivilegedExceptionAction<SaslClientHandler>() {
193                 @Override
194                 public SaslClientHandler run() throws IOException {
195                   return getSaslHandler(realTicket, bootstrap);
196                 }
197               });
198               if (saslHandler != null) {
199                 // Sasl connect is successful. Let's set up Sasl channel handler
200                 channel.pipeline().addFirst(saslHandler);
201               } else {
202                 // fall back to simple auth because server told us so.
203                 authMethod = AuthMethod.SIMPLE;
204                 useSasl = false;
205               }
206             } else {
207               startHBaseConnection(f.channel());
208             }
209           }
210         });
211   }
212 
213   /**
214    * Start HBase connection
215    * @param ch channel to start connection on
216    */
217   private void startHBaseConnection(Channel ch) {
218     ch.pipeline().addLast("frameDecoder",
219       new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
220     ch.pipeline().addLast(new AsyncServerResponseHandler(this));
221     try {
222       writeChannelHeader(ch).addListener(new GenericFutureListener<ChannelFuture>() {
223         @Override
224         public void operationComplete(ChannelFuture future) throws Exception {
225           if (!future.isSuccess()) {
226             close(future.cause());
227             return;
228           }
229           List<AsyncCall> callsToWrite;
230           synchronized (pendingCalls) {
231             connected = true;
232             callsToWrite = new ArrayList<AsyncCall>(pendingCalls.values());
233           }
234           for (AsyncCall call : callsToWrite) {
235             writeRequest(call);
236           }
237         }
238       });
239     } catch (IOException e) {
240       close(e);
241     }
242   }
243 
244   /**
245    * Start HBase connection with sasl encryption
246    * @param ch channel to start connection on
247    */
248   private void startConnectionWithEncryption(Channel ch) {
249     // for rpc encryption, the order of ChannelInboundHandler should be:
250     // LengthFieldBasedFrameDecoder->SaslClientHandler->LengthFieldBasedFrameDecoder
251     // Don't skip the first 4 bytes for length in beforeUnwrapDecoder,
252     // SaslClientHandler will handler this
253     ch.pipeline().addFirst("beforeUnwrapDecoder",
254         new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 0));
255     ch.pipeline().addLast("afterUnwrapDecoder",
256         new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
257     ch.pipeline().addLast(new AsyncServerResponseHandler(this));
258     List<AsyncCall> callsToWrite;
259     synchronized (pendingCalls) {
260       connected = true;
261       callsToWrite = new ArrayList<AsyncCall>(pendingCalls.values());
262     }
263     for (AsyncCall call : callsToWrite) {
264       writeRequest(call);
265     }
266   }
267 
268   /**
269    * Get SASL handler
270    * @param bootstrap to reconnect to
271    * @return new SASL handler
272    * @throws java.io.IOException if handler failed to create
273    */
274   private SaslClientHandler getSaslHandler(final UserGroupInformation realTicket,
275       final Bootstrap bootstrap) throws IOException {
276     return new SaslClientHandler(realTicket, authMethod, token, serverPrincipal,
277         client.fallbackAllowed,
278         client.conf.get("hbase.rpc.protection",
279           SaslUtil.QualityOfProtection.AUTHENTICATION.name().toLowerCase()),
280         getChannelHeaderBytes(authMethod),
281         new SaslClientHandler.SaslExceptionHandler() {
282           @Override
283           public void handle(int retryCount, Random random, Throwable cause) {
284             try {
285               // Handle Sasl failure. Try to potentially get new credentials
286               handleSaslConnectionFailure(retryCount, cause, realTicket);
287 
288               // Try to reconnect
289               client.newTimeout(new TimerTask() {
290                 @Override
291                 public void run(Timeout timeout) throws Exception {
292                   connect(bootstrap);
293                 }
294               }, random.nextInt(reloginMaxBackoff) + 1, TimeUnit.MILLISECONDS);
295             } catch (IOException | InterruptedException e) {
296               close(e);
297             }
298           }
299         }, new SaslClientHandler.SaslSuccessfulConnectHandler() {
300           @Override
301           public void onSuccess(Channel channel) {
302             startHBaseConnection(channel);
303           }
304 
305           @Override
306           public void onSaslProtectionSucess(Channel channel) {
307             startConnectionWithEncryption(channel);
308           }
309         });
310   }
311 
312   /**
313    * Retry to connect or close
314    *
315    * @param bootstrap      to connect with
316    * @param connectCounter amount of tries
317    * @param e              exception of fail
318    */
319   private void retryOrClose(final Bootstrap bootstrap, int connectCounter, Throwable e) {
320     if (connectCounter < client.maxRetries) {
321       client.newTimeout(new TimerTask() {
322         @Override public void run(Timeout timeout) throws Exception {
323           connect(bootstrap);
324         }
325       }, client.failureSleep, TimeUnit.MILLISECONDS);
326     } else {
327       client.failedServers.addToFailedServers(address);
328       close(e);
329     }
330   }
331 
332   /**
333    * Calls method on channel
334    * @param method to call
335    * @param controller to run call with
336    * @param request to send
337    * @param responsePrototype to construct response with
338    */
339   public Promise<Message> callMethod(final Descriptors.MethodDescriptor method,
340       final PayloadCarryingRpcController controller, final Message request,
341       final Message responsePrototype, MetricsConnection.CallStats callStats) {
342     final AsyncCall call = new AsyncCall(channel.eventLoop(), client.callIdCnt.getAndIncrement(),
343         method, request, controller, responsePrototype, callStats);
344     controller.notifyOnCancel(new RpcCallback<Object>() {
345       @Override
346       public void run(Object parameter) {
347         // TODO: do not need to call AsyncCall.setFailed?
348         synchronized (pendingCalls) {
349           pendingCalls.remove(call.id);
350         }
351       }
352     });
353     // TODO: this should be handled by PayloadCarryingRpcController.
354     if (controller.isCanceled()) {
355       // To finish if the call was cancelled before we set the notification (race condition)
356       call.cancel(true);
357       return call;
358     }
359 
360     synchronized (pendingCalls) {
361       if (closed) {
362         Promise<Message> promise = channel.eventLoop().newPromise();
363         promise.setFailure(new ConnectException());
364         return promise;
365       }
366       pendingCalls.put(call.id, call);
367       // Add timeout for cleanup if none is present
368       if (cleanupTimer == null && call.getRpcTimeout() > 0) {
369         cleanupTimer = client.newTimeout(timeoutTask, call.getRpcTimeout(), TimeUnit.MILLISECONDS);
370       }
371       if (!connected) {
372         return call;
373       }
374     }
375     writeRequest(call);
376     return call;
377   }
378 
379   AsyncCall removePendingCall(int id) {
380     synchronized (pendingCalls) {
381       return pendingCalls.remove(id);
382     }
383   }
384 
385   /**
386    * Write the channel header
387    * @param channel to write to
388    * @return future of write
389    * @throws java.io.IOException on failure to write
390    */
391   private ChannelFuture writeChannelHeader(Channel channel) throws IOException {
392     RPCProtos.ConnectionHeader header = getChannelHeader(authMethod);
393     int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(header);
394 
395     ByteBuf b = channel.alloc().directBuffer(totalSize);
396 
397     b.writeInt(header.getSerializedSize());
398     b.writeBytes(header.toByteArray());
399 
400     return channel.writeAndFlush(b);
401   }
402 
403   private byte[] getChannelHeaderBytes(AuthMethod authMethod) {
404     RPCProtos.ConnectionHeader header = getChannelHeader(authMethod);
405     ByteBuffer b = ByteBuffer.allocate(header.getSerializedSize() + 4);
406     b.putInt(header.getSerializedSize());
407     b.put(header.toByteArray());
408     return b.array();
409   }
410 
411   private RPCProtos.ConnectionHeader getChannelHeader(AuthMethod authMethod) {
412     RPCProtos.ConnectionHeader.Builder headerBuilder = RPCProtos.ConnectionHeader.newBuilder()
413         .setServiceName(serviceName);
414 
415     RPCProtos.UserInformation userInfoPB = buildUserInfo(ticket.getUGI(), authMethod);
416     if (userInfoPB != null) {
417       headerBuilder.setUserInfo(userInfoPB);
418     }
419 
420     if (client.codec != null) {
421       headerBuilder.setCellBlockCodecClass(client.codec.getClass().getCanonicalName());
422     }
423     if (client.compressor != null) {
424       headerBuilder.setCellBlockCompressorClass(client.compressor.getClass().getCanonicalName());
425     }
426 
427     headerBuilder.setVersionInfo(ProtobufUtil.getVersionInfo());
428     return headerBuilder.build();
429   }
430 
431   /**
432    * Write request to channel
433    * @param call to write
434    */
435   private void writeRequest(final AsyncCall call) {
436     try {
437       final RPCProtos.RequestHeader.Builder requestHeaderBuilder = RPCProtos.RequestHeader
438           .newBuilder();
439       requestHeaderBuilder.setCallId(call.id).setMethodName(call.method.getName())
440           .setRequestParam(call.param != null);
441 
442       if (Trace.isTracing()) {
443         Span s = Trace.currentSpan();
444         requestHeaderBuilder.setTraceInfo(TracingProtos.RPCTInfo.newBuilder()
445             .setParentId(s.getSpanId()).setTraceId(s.getTraceId()));
446       }
447 
448       ByteBuffer cellBlock = client.buildCellBlock(call.controller.cellScanner());
449       if (cellBlock != null) {
450         final RPCProtos.CellBlockMeta.Builder cellBlockBuilder = RPCProtos.CellBlockMeta
451             .newBuilder();
452         cellBlockBuilder.setLength(cellBlock.limit());
453         requestHeaderBuilder.setCellBlockMeta(cellBlockBuilder.build());
454       }
455       // Only pass priority if there one. Let zero be same as no priority.
456       if (call.controller.getPriority() != PayloadCarryingRpcController.PRIORITY_UNSET) {
457         requestHeaderBuilder.setPriority(call.controller.getPriority());
458       }
459       requestHeaderBuilder.setTimeout(call.rpcTimeout > Integer.MAX_VALUE ?
460           Integer.MAX_VALUE : (int)call.rpcTimeout);
461       RPCProtos.RequestHeader rh = requestHeaderBuilder.build();
462 
463       int totalSize = IPCUtil.getTotalSizeWhenWrittenDelimited(rh, call.param);
464       if (cellBlock != null) {
465         totalSize += cellBlock.remaining();
466       }
467 
468       ByteBuf b = channel.alloc().directBuffer(4 + totalSize);
469       try (ByteBufOutputStream out = new ByteBufOutputStream(b)) {
470         call.callStats.setRequestSizeBytes(IPCUtil.write(out, rh, call.param, cellBlock));
471       }
472 
473       channel.writeAndFlush(b).addListener(new CallWriteListener(this, call.id));
474     } catch (IOException e) {
475       close(e);
476     }
477   }
478 
479   /**
480    * Set up server authorization
481    * @throws java.io.IOException if auth setup failed
482    */
483   private void setupAuthorization() throws IOException {
484     SecurityInfo securityInfo = SecurityInfo.getInfo(serviceName);
485     this.useSasl = client.userProvider.isHBaseSecurityEnabled();
486 
487     this.token = null;
488     if (useSasl && securityInfo != null) {
489       AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind();
490       if (tokenKind != null) {
491         TokenSelector<? extends TokenIdentifier> tokenSelector = TOKEN_HANDDLERS.get(tokenKind);
492         if (tokenSelector != null) {
493           token = tokenSelector.selectToken(new Text(client.clusterId),
494             ticket.getUGI().getTokens());
495         } else if (LOG.isDebugEnabled()) {
496           LOG.debug("No token selector found for type " + tokenKind);
497         }
498       }
499       String serverKey = securityInfo.getServerPrincipal();
500       if (serverKey == null) {
501         throw new IOException("Can't obtain server Kerberos config key from SecurityInfo");
502       }
503       this.serverPrincipal = SecurityUtil.getServerPrincipal(client.conf.get(serverKey),
504         address.getAddress().getCanonicalHostName().toLowerCase());
505       if (LOG.isDebugEnabled()) {
506         LOG.debug("RPC Server Kerberos principal name for service=" + serviceName + " is "
507             + serverPrincipal);
508       }
509     }
510 
511     if (!useSasl) {
512       authMethod = AuthMethod.SIMPLE;
513     } else if (token != null) {
514       authMethod = AuthMethod.DIGEST;
515     } else {
516       authMethod = AuthMethod.KERBEROS;
517     }
518 
519     if (LOG.isDebugEnabled()) {
520       LOG.debug(
521         "Use " + authMethod + " authentication for service " + serviceName + ", sasl=" + useSasl);
522     }
523     reloginMaxBackoff = client.conf.getInt("hbase.security.relogin.maxbackoff", 5000);
524   }
525 
526   /**
527    * Build the user information
528    * @param ugi User Group Information
529    * @param authMethod Authorization method
530    * @return UserInformation protobuf
531    */
532   private RPCProtos.UserInformation buildUserInfo(UserGroupInformation ugi, AuthMethod authMethod) {
533     if (ugi == null || authMethod == AuthMethod.DIGEST) {
534       // Don't send user for token auth
535       return null;
536     }
537     RPCProtos.UserInformation.Builder userInfoPB = RPCProtos.UserInformation.newBuilder();
538     if (authMethod == AuthMethod.KERBEROS) {
539       // Send effective user for Kerberos auth
540       userInfoPB.setEffectiveUser(ugi.getUserName());
541     } else if (authMethod == AuthMethod.SIMPLE) {
542       // Send both effective user and real user for simple auth
543       userInfoPB.setEffectiveUser(ugi.getUserName());
544       if (ugi.getRealUser() != null) {
545         userInfoPB.setRealUser(ugi.getRealUser().getUserName());
546       }
547     }
548     return userInfoPB.build();
549   }
550 
551   /**
552    * Create connection preamble
553    * @param byteBuf to write to
554    * @param authMethod to write
555    */
556   private void createPreamble(ByteBuf byteBuf, AuthMethod authMethod) {
557     byteBuf.writeBytes(HConstants.RPC_HEADER);
558     byteBuf.writeByte(HConstants.RPC_CURRENT_VERSION);
559     byteBuf.writeByte(authMethod.code);
560   }
561 
562   private void close0(Throwable e) {
563     List<AsyncCall> toCleanup;
564     synchronized (pendingCalls) {
565       if (closed) {
566         return;
567       }
568       closed = true;
569       toCleanup = new ArrayList<AsyncCall>(pendingCalls.values());
570       pendingCalls.clear();
571     }
572     IOException closeException = null;
573     if (e != null) {
574       if (e instanceof IOException) {
575         closeException = (IOException) e;
576       } else {
577         closeException = new IOException(e);
578       }
579     }
580     // log the info
581     if (LOG.isDebugEnabled() && closeException != null) {
582       LOG.debug(name + ": closing ipc connection to " + address, closeException);
583     }
584     if (cleanupTimer != null) {
585       cleanupTimer.cancel();
586       cleanupTimer = null;
587     }
588     for (AsyncCall call : toCleanup) {
589       call.setFailed(closeException != null ? closeException
590           : new ConnectionClosingException(
591               "Call id=" + call.id + " on server " + address + " aborted: connection is closing"));
592     }
593     channel.disconnect().addListener(ChannelFutureListener.CLOSE);
594     if (LOG.isDebugEnabled()) {
595       LOG.debug(name + ": closed");
596     }
597   }
598 
599   /**
600    * Close connection
601    * @param e exception on close
602    */
603   public void close(final Throwable e) {
604     client.removeConnection(this);
605 
606     // Move closing from the requesting thread to the channel thread
607     if (channel.eventLoop().inEventLoop()) {
608       close0(e);
609     } else {
610       channel.eventLoop().execute(new Runnable() {
611         @Override
612         public void run() {
613           close0(e);
614         }
615       });
616     }
617   }
618 
619   /**
620    * Clean up calls.
621    */
622   private void cleanupCalls() {
623     List<AsyncCall> toCleanup = new ArrayList<AsyncCall>();
624     long currentTime = EnvironmentEdgeManager.currentTime();
625     long nextCleanupTaskDelay = -1L;
626     synchronized (pendingCalls) {
627       for (Iterator<AsyncCall> iter = pendingCalls.values().iterator(); iter.hasNext();) {
628         AsyncCall call = iter.next();
629         long timeout = call.getRpcTimeout();
630         if (timeout > 0) {
631           if (currentTime - call.getStartTime() >= timeout) {
632             iter.remove();
633             toCleanup.add(call);
634           } else {
635             if (nextCleanupTaskDelay < 0 || timeout < nextCleanupTaskDelay) {
636               nextCleanupTaskDelay = timeout;
637             }
638           }
639         }
640       }
641       if (nextCleanupTaskDelay > 0) {
642         cleanupTimer = client.newTimeout(timeoutTask, nextCleanupTaskDelay, TimeUnit.MILLISECONDS);
643       } else {
644         cleanupTimer = null;
645       }
646     }
647     for (AsyncCall call : toCleanup) {
648       call.setFailed(new CallTimeoutException("Call id=" + call.id + ", waitTime="
649           + (currentTime - call.getStartTime()) + ", rpcTimeout=" + call.getRpcTimeout()));
650     }
651   }
652 
653   /**
654    * Check if the connection is alive
655    * @return true if alive
656    */
657   public boolean isAlive() {
658     return channel.isOpen();
659   }
660 
661   /**
662    * Check if user should authenticate over Kerberos
663    * @return true if should be authenticated over Kerberos
664    * @throws java.io.IOException on failure of check
665    */
666   private synchronized boolean shouldAuthenticateOverKrb() throws IOException {
667     UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
668     UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
669     UserGroupInformation realUser = currentUser.getRealUser();
670     return authMethod == AuthMethod.KERBEROS && loginUser != null &&
671       // Make sure user logged in using Kerberos either keytab or TGT
672       loginUser.hasKerberosCredentials() &&
673       // relogin only in case it is the login user (e.g. JT)
674       // or superuser (like oozie).
675       (loginUser.equals(currentUser) || loginUser.equals(realUser));
676   }
677 
678   /**
679    * If multiple clients with the same principal try to connect to the same server at the same time,
680    * the server assumes a replay attack is in progress. This is a feature of kerberos. In order to
681    * work around this, what is done is that the client backs off randomly and tries to initiate the
682    * connection again. The other problem is to do with ticket expiry. To handle that, a relogin is
683    * attempted.
684    * <p>
685    * The retry logic is governed by the {@link #shouldAuthenticateOverKrb} method. In case when the
686    * user doesn't have valid credentials, we don't need to retry (from cache or ticket). In such
687    * cases, it is prudent to throw a runtime exception when we receive a SaslException from the
688    * underlying authentication implementation, so there is no retry from other high level (for eg,
689    * HCM or HBaseAdmin).
690    * </p>
691    * @param currRetries retry count
692    * @param ex exception describing fail
693    * @param user which is trying to connect
694    * @throws java.io.IOException if IO fail
695    * @throws InterruptedException if thread is interrupted
696    */
697   private void handleSaslConnectionFailure(final int currRetries, final Throwable ex,
698       final UserGroupInformation user) throws IOException, InterruptedException {
699     user.doAs(new PrivilegedExceptionAction<Void>() {
700       @Override
701       public Void run() throws IOException, InterruptedException {
702         if (shouldAuthenticateOverKrb()) {
703           if (currRetries < MAX_SASL_RETRIES) {
704             LOG.debug("Exception encountered while connecting to the server : " + ex);
705             // try re-login
706             if (UserGroupInformation.isLoginKeytabBased()) {
707               UserGroupInformation.getLoginUser().reloginFromKeytab();
708             } else {
709               UserGroupInformation.getLoginUser().reloginFromTicketCache();
710             }
711 
712             // Should reconnect
713             return null;
714           } else {
715             String msg = "Couldn't setup connection for "
716                 + UserGroupInformation.getLoginUser().getUserName() + " to " + serverPrincipal;
717             LOG.warn(msg);
718             throw (IOException) new IOException(msg).initCause(ex);
719           }
720         } else {
721           LOG.warn("Exception encountered while connecting to " + "the server : " + ex);
722         }
723         if (ex instanceof RemoteException) {
724           throw (RemoteException) ex;
725         }
726         if (ex instanceof SaslException) {
727           String msg = "SASL authentication failed."
728               + " The most likely cause is missing or invalid credentials." + " Consider 'kinit'.";
729           LOG.fatal(msg, ex);
730           throw new RuntimeException(msg, ex);
731         }
732         throw new IOException(ex);
733       }
734     });
735   }
736 
737   public int getConnectionHashCode() {
738     return ConnectionId.hashCode(ticket, serviceName, address);
739   }
740 
741   @Override
742   public int hashCode() {
743     return getConnectionHashCode();
744   }
745 
746   @Override
747   public boolean equals(Object obj) {
748     if (obj instanceof AsyncRpcChannel) {
749       AsyncRpcChannel channel = (AsyncRpcChannel) obj;
750       return channel.hashCode() == obj.hashCode();
751     }
752     return false;
753   }
754 
755   @Override
756   public String toString() {
757     return this.address.toString() + "/" + this.serviceName + "/" + this.ticket;
758   }
759 
760   /**
761    * Listens to call writes and fails if write failed
762    */
763   private static final class CallWriteListener implements ChannelFutureListener {
764     private final AsyncRpcChannel rpcChannel;
765     private final int id;
766 
767     public CallWriteListener(AsyncRpcChannel asyncRpcChannel, int id) {
768       this.rpcChannel = asyncRpcChannel;
769       this.id = id;
770     }
771 
772     @Override
773     public void operationComplete(ChannelFuture future) throws Exception {
774       if (!future.isSuccess()) {
775         AsyncCall call = rpcChannel.removePendingCall(id);
776         if (call != null) {
777           if (future.cause() instanceof IOException) {
778             call.setFailed((IOException) future.cause());
779           } else {
780             call.setFailed(new IOException(future.cause()));
781           }
782         }
783       }
784     }
785   }
786 }