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