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