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 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   * Netty RPC channel
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    // Contains the channel to work with.
98    // Only exists when connected
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   // NOTE: closed and connected flags below are only changed when a lock on pendingCalls
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    * Constructor for netty RPC channel
131    *
132    * @param bootstrap to construct channel on
133    * @param client    to connect with
134    * @param ticket of user which uses connection
135    * @param serviceName name of service to connect to
136    * @param address to connect to
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    * Connect to channel
157    *
158    * @param bootstrap to connect to
159    * @return future of connection
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                 // Sasl connect is successful. Let's set up Sasl channel handler
197                 channel.pipeline().addFirst(saslHandler);
198               } else {
199                 // fall back to simple auth because server told us so.
200                 authMethod = AuthMethod.SIMPLE;
201                 useSasl = false;
202               }
203             } else {
204               startHBaseConnection(f.channel());
205             }
206           }
207         });
208   }
209 
210   /**
211    * Start HBase connection
212    *
213    * @param ch channel to start connection on
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    * Get SASL handler
244    * @param bootstrap to reconnect to
245    * @return new SASL handler
246    * @throws java.io.IOException if handler failed to create
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               // Handle Sasl failure. Try to potentially get new credentials
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    * Retry to connect or close
276    *
277    * @param bootstrap      to connect with
278    * @param failureCount   failure count
279    * @param e              exception of fail
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    * Calls method on channel
298    * @param method to call
299    * @param controller to run call with
300    * @param request to send
301    * @param responsePrototype to construct response with
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         // TODO: do not need to call AsyncCall.setFailed?
313         synchronized (pendingCalls) {
314           pendingCalls.remove(call.id);
315         }
316       }
317     });
318     // TODO: this should be handled by PayloadCarryingRpcController.
319     if (controller.isCanceled()) {
320       // To finish if the call was cancelled before we set the notification (race condition)
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       // Add timeout for cleanup if none is present
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    * Write the channel header
354    *
355    * @param channel to write to
356    * @return future of write
357    * @throws java.io.IOException on failure to write
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    * Write request to channel
391    *
392    * @param call    to write
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       // Only pass priority if there one.  Let zero be same as no priority.
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    * Set up server authorization
439    *
440    * @throws java.io.IOException if auth setup failed
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    * Build the user information
487    *
488    * @param ugi        User Group Information
489    * @param authMethod Authorization method
490    * @return UserInformation protobuf
491    */
492   private RPCProtos.UserInformation buildUserInfo(UserGroupInformation ugi, AuthMethod authMethod) {
493     if (ugi == null || authMethod == AuthMethod.DIGEST) {
494       // Don't send user for token auth
495       return null;
496     }
497     RPCProtos.UserInformation.Builder userInfoPB = RPCProtos.UserInformation.newBuilder();
498     if (authMethod == AuthMethod.KERBEROS) {
499       // Send effective user for Kerberos auth
500       userInfoPB.setEffectiveUser(ugi.getUserName());
501     } else if (authMethod == AuthMethod.SIMPLE) {
502       //Send both effective user and real user for simple auth
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    * Create connection preamble
513    *
514    * @param byteBuf    to write to
515    * @param authMethod to write
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    * Close connection
525    *
526    * @param e exception on close
527    */
528   public void close(final Throwable e) {
529     client.removeConnection(this);
530 
531     // Move closing from the requesting thread to the channel thread
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         // log the info
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    * Clean up calls.
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    * Check if the connection is alive
610    *
611    * @return true if alive
612    */
613   public boolean isAlive() {
614     return channel.isOpen();
615   }
616 
617   /**
618    * Check if user should authenticate over Kerberos
619    *
620    * @return true if should be authenticated over Kerberos
621    * @throws java.io.IOException on failure of check
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         //Make sure user logged in using Kerberos either keytab or TGT
630         loginUser.hasKerberosCredentials() &&
631         // relogin only in case it is the login user (e.g. JT)
632         // or superuser (like oozie).
633         (loginUser.equals(currentUser) || loginUser.equals(realUser));
634   }
635 
636   /**
637    * If multiple clients with the same principal try to connect
638    * to the same server at the same time, the server assumes a
639    * replay attack is in progress. This is a feature of kerberos.
640    * In order to work around this, what is done is that the client
641    * backs off randomly and tries to initiate the connection
642    * again.
643    * The other problem is to do with ticket expiry. To handle that,
644    * a relogin is attempted.
645    * <p>
646    * The retry logic is governed by the {@link #shouldAuthenticateOverKrb}
647    * method. In case when the user doesn't have valid credentials, we don't
648    * need to retry (from cache or ticket). In such cases, it is prudent to
649    * throw a runtime exception when we receive a SaslException from the
650    * underlying authentication implementation, so there is no retry from
651    * other high level (for eg, HCM or HBaseAdmin).
652    * </p>
653    *
654    * @param currRetries retry count
655    * @param ex          exception describing fail
656    * @param user        which is trying to connect
657    * @throws java.io.IOException  if IO fail
658    * @throws InterruptedException if thread is interrupted
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             //try re-login
668             if (UserGroupInformation.isLoginKeytabBased()) {
669               UserGroupInformation.getLoginUser().reloginFromKeytab();
670             } else {
671               UserGroupInformation.getLoginUser().reloginFromTicketCache();
672             }
673 
674             // Should reconnect
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    * Listens to call writes and fails if write failed
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 }