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  
19  package org.apache.hadoop.hbase.ipc;
20  
21  import static org.apache.hadoop.hbase.ipc.RpcClient.SPECIFIC_WRITE_THREAD;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.io.IOException;
27  import java.net.InetSocketAddress;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Random;
32  import java.util.concurrent.Callable;
33  import java.util.concurrent.atomic.AtomicBoolean;
34  import java.util.concurrent.atomic.AtomicReference;
35  import java.util.concurrent.locks.ReadWriteLock;
36  import java.util.concurrent.locks.ReentrantReadWriteLock;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.hbase.CellScanner;
42  import org.apache.hadoop.hbase.HBaseConfiguration;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.codec.Codec;
45  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
46  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EchoRequestProto;
47  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EchoResponseProto;
48  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EmptyRequestProto;
49  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EmptyResponseProto;
50  import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
51  import org.apache.hadoop.hbase.security.User;
52  import org.apache.hadoop.hbase.testclassification.IntegrationTests;
53  import org.apache.hadoop.hbase.util.Pair;
54  import org.apache.hadoop.hbase.util.Threads;
55  import org.junit.Ignore;
56  import org.junit.Test;
57  import org.junit.experimental.categories.Category;
58  import com.google.common.collect.Lists;
59  import com.google.protobuf.BlockingService;
60  import com.google.protobuf.Message;
61  import com.google.protobuf.RpcController;
62  import com.google.protobuf.ServiceException;
63  import com.google.protobuf.Descriptors.MethodDescriptor;
64  
65  @Category(IntegrationTests.class)
66  public class IntegrationTestRpcClient {
67  
68    private static final Log LOG = LogFactory.getLog(IntegrationTestRpcClient.class);
69  
70    private final Configuration conf;
71  
72    private int numIterations = 10;
73  
74    public IntegrationTestRpcClient() {
75      conf = HBaseConfiguration.create();
76    }
77  
78    static class TestRpcServer extends RpcServer {
79  
80      TestRpcServer(Configuration conf) throws IOException {
81        this(new FifoRpcScheduler(conf, 1), conf);
82      }
83  
84      TestRpcServer(RpcScheduler scheduler, Configuration conf) throws IOException {
85        super(null, "testRpcServer", Lists
86            .newArrayList(new BlockingServiceAndInterface(SERVICE, null)), new InetSocketAddress(
87            "localhost", 0), conf, scheduler);
88      }
89  
90      @Override
91      public Pair<Message, CellScanner> call(BlockingService service, MethodDescriptor md,
92          Message param, CellScanner cellScanner, long receiveTime, MonitoredRPCHandler status)
93          throws IOException {
94        return super.call(service, md, param, cellScanner, receiveTime, status);
95      }
96    }
97  
98    static final BlockingService SERVICE =
99        TestRpcServiceProtos.TestProtobufRpcProto
100       .newReflectiveBlockingService(new TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface() {
101 
102         @Override
103         public EmptyResponseProto ping(RpcController controller, EmptyRequestProto request)
104             throws ServiceException {
105           return null;
106         }
107 
108         @Override
109         public EmptyResponseProto error(RpcController controller, EmptyRequestProto request)
110             throws ServiceException {
111           return null;
112         }
113 
114         @Override
115         public EchoResponseProto echo(RpcController controller, EchoRequestProto request)
116             throws ServiceException {
117           return EchoResponseProto.newBuilder().setMessage(request.getMessage()).build();
118         }
119       });
120 
121   protected AbstractRpcClient createRpcClient(Configuration conf, boolean isSyncClient) {
122     return isSyncClient ?
123         new RpcClientImpl(conf, HConstants.CLUSTER_ID_DEFAULT) :
124           new AsyncRpcClient(conf) {
125           @Override
126           Codec getCodec() {
127             return null;
128           }
129         };
130   }
131 
132   static String BIG_PAYLOAD;
133 
134   static {
135     StringBuilder builder = new StringBuilder();
136 
137     while (builder.length() < 1024 * 1024) { // 2 MB
138       builder.append("big.payload.");
139     }
140 
141     BIG_PAYLOAD = builder.toString();
142   }
143 
144   class Cluster {
145     Random random = new Random();
146     ReadWriteLock lock = new ReentrantReadWriteLock();
147     HashMap<InetSocketAddress, TestRpcServer> rpcServers = new HashMap<>();
148     List<TestRpcServer> serverList = new ArrayList<>();
149     int maxServers;
150     int minServers;
151 
152     Cluster(int minServers, int maxServers) {
153       this.minServers = minServers;
154       this.maxServers = maxServers;
155     }
156 
157     TestRpcServer startServer() throws IOException {
158       lock.writeLock().lock();
159       try {
160         if (rpcServers.size() >= maxServers) {
161           return null;
162         }
163 
164         TestRpcServer rpcServer = new TestRpcServer(conf);
165         rpcServer.start();
166         InetSocketAddress address = rpcServer.getListenerAddress();        
167         if (address == null) {
168           throw new IOException("Listener channel is closed");
169         }
170         rpcServers.put(address, rpcServer);
171         serverList.add(rpcServer);
172         LOG.info("Started server: " + address);
173         return rpcServer;
174       } finally {
175         lock.writeLock().unlock();
176       }
177     }
178 
179     void stopRandomServer() throws Exception {
180       lock.writeLock().lock();
181       TestRpcServer rpcServer = null;
182       try {
183         if (rpcServers.size() <= minServers) {
184           return;
185         }
186         int size = rpcServers.size();
187         int rand = random.nextInt(size);
188         rpcServer = serverList.remove(rand);
189         InetSocketAddress address = rpcServer.getListenerAddress();
190         if (address == null) {
191           // Throw exception here. We can't remove this instance from the server map because
192           // we no longer have access to its map key
193           throw new IOException("Listener channel is closed");
194         }
195         rpcServers.remove(address);
196 
197         if (rpcServer != null) {
198           stopServer(rpcServer);
199         }
200       } finally {
201         lock.writeLock().unlock();
202       }
203     }
204 
205     void stopServer(TestRpcServer rpcServer) throws InterruptedException {
206       InetSocketAddress address = rpcServer.getListenerAddress();
207       LOG.info("Stopping server: " + address);
208       rpcServer.stop();
209       rpcServer.join();
210       LOG.info("Stopped server: " + address);
211     }
212 
213     void stopRunning() throws InterruptedException {
214       lock.writeLock().lock();
215       try {
216         for (TestRpcServer rpcServer : serverList) {
217           stopServer(rpcServer);
218         }
219 
220       } finally {
221         lock.writeLock().unlock();
222       }
223     }
224 
225     TestRpcServer getRandomServer() {
226       lock.readLock().lock();
227       try {
228         int size = rpcServers.size();
229         int rand = random.nextInt(size);
230         return serverList.get(rand);
231       } finally {
232         lock.readLock().unlock();
233       }
234     }
235   }
236 
237   static class MiniChaosMonkey extends Thread {
238     AtomicBoolean running = new  AtomicBoolean(true);
239     Random random = new Random();
240     AtomicReference<Exception> exception = new AtomicReference<>(null);
241     Cluster cluster;
242 
243     public MiniChaosMonkey(Cluster cluster) {
244       this.cluster = cluster;
245     }
246 
247     @Override
248     public void run() {
249       while (running.get()) {
250         switch (random.nextInt() % 2) {
251         case 0: //start a server
252           try {
253             cluster.startServer();
254           } catch (Exception e) {
255             LOG.warn(e);
256             exception.compareAndSet(null, e);
257           }
258           break;
259 
260         case 1: // stop a server
261           try {
262             cluster.stopRandomServer();
263           } catch (Exception e) {
264             LOG.warn(e);
265             exception.compareAndSet(null, e);
266           }
267         default:
268         }
269 
270         Threads.sleep(100);
271       }
272     }
273 
274     void stopRunning() {
275       running.set(false);
276     }
277 
278     void rethrowException() throws Exception {
279       if (exception.get() != null) {
280         throw exception.get();
281       }
282     }
283   }
284 
285   static class SimpleClient extends Thread {
286     AbstractRpcClient rpcClient;
287     AtomicBoolean running = new  AtomicBoolean(true);
288     AtomicBoolean sending = new AtomicBoolean(false);
289     AtomicReference<Throwable> exception = new AtomicReference<>(null);
290     Cluster cluster;
291     String id;
292     long numCalls = 0;
293     Random random = new Random();
294 
295     public SimpleClient(Cluster cluster, AbstractRpcClient rpcClient, String id) {
296       this.cluster = cluster;
297       this.rpcClient = rpcClient;
298       this.id = id;
299     }
300 
301     @Override
302     public void run() {
303       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
304 
305       while (running.get()) {
306         boolean isBigPayload = random.nextBoolean();
307         String message = isBigPayload ? BIG_PAYLOAD : id + numCalls;
308         EchoRequestProto param = EchoRequestProto.newBuilder().setMessage(message).build();
309         EchoResponseProto ret = EchoResponseProto.newBuilder().setMessage("foo").build();
310 
311         TestRpcServer server = cluster.getRandomServer();
312         try {
313           User user = User.getCurrent();
314           InetSocketAddress address = server.getListenerAddress();
315           if (address == null) {
316             throw new IOException("Listener channel is closed");
317           }
318           sending.set(true);
319           ret = (EchoResponseProto)
320               rpcClient.callBlockingMethod(md, null, param, ret, user, address);
321         } catch (Exception e) {
322           LOG.warn(e);
323           continue; // expected in case connection is closing or closed
324         }
325 
326         try {
327           assertNotNull(ret);
328           assertEquals(message, ret.getMessage());
329         } catch (Throwable t) {
330           exception.compareAndSet(null, t);
331         }
332 
333         numCalls++;
334       }
335     }
336 
337     void stopRunning() {
338       running.set(false);
339     }
340     boolean isSending() {
341       return sending.get();
342     }
343 
344     void rethrowException() throws Throwable {
345       if (exception.get() != null) {
346         throw exception.get();
347       }
348     }
349   }
350 
351   /*
352   Test that not started connections are successfully removed from connection pool when
353   rpc client is closing.
354    */
355   @Test (timeout = 30000)
356   public void testRpcWithWriteThread() throws IOException, InterruptedException {
357     LOG.info("Starting test");
358     Cluster cluster = new Cluster(1, 1);
359     cluster.startServer();
360     conf.setBoolean(SPECIFIC_WRITE_THREAD, true);
361     for(int i = 0; i <1000; i++) {
362       AbstractRpcClient rpcClient = createRpcClient(conf, true);
363       SimpleClient client = new SimpleClient(cluster, rpcClient, "Client1");
364       client.start();
365       while(!client.isSending()) {
366         Thread.sleep(1);
367       }
368       client.stopRunning();
369       rpcClient.close();
370     }
371   }
372 
373 
374   @Test (timeout = 900000)
375   public void testRpcWithChaosMonkeyWithSyncClient() throws Throwable {
376     for (int i = 0; i < numIterations; i++) {
377       TimeoutThread.runWithTimeout(new Callable<Void>() {
378         @Override
379         public Void call() throws Exception {
380           try {
381             testRpcWithChaosMonkey(true);
382           } catch (Throwable e) {
383             if (e instanceof Exception) {
384               throw (Exception)e;
385             } else {
386               throw new Exception(e);
387             }
388           }
389           return null;
390         }
391       }, 90000);
392     }
393   }
394 
395   @Test (timeout = 900000)
396   @Ignore // TODO: test fails with async client
397   public void testRpcWithChaosMonkeyWithAsyncClient() throws Throwable {
398     for (int i = 0; i < numIterations; i++) {
399       TimeoutThread.runWithTimeout(new Callable<Void>() {
400         @Override
401         public Void call() throws Exception {
402           try {
403             testRpcWithChaosMonkey(false);
404           } catch (Throwable e) {
405             if (e instanceof Exception) {
406               throw (Exception)e;
407             } else {
408               throw new Exception(e);
409             }
410           }
411           return null;
412         }
413       }, 90000);
414     }
415   }
416 
417   static class TimeoutThread extends Thread {
418     long timeout;
419     public TimeoutThread(long timeout) {
420       this.timeout = timeout;
421     }
422 
423     @Override
424     public void run() {
425       try {
426         Thread.sleep(timeout);
427         Threads.printThreadInfo(System.err, "TEST TIMEOUT STACK DUMP");
428         System.exit(1); // a timeout happened
429       } catch (InterruptedException e) {
430         // this is what we want
431       }
432     }
433 
434     // runs in the same thread context but injects a timeout thread which will exit the JVM on
435     // timeout
436     static void runWithTimeout(Callable<?> callable, long timeout) throws Exception {
437       TimeoutThread thread = new TimeoutThread(timeout);
438       thread.start();
439       callable.call();
440       thread.interrupt();
441     }
442   }
443 
444   public void testRpcWithChaosMonkey(boolean isSyncClient) throws Throwable {
445     LOG.info("Starting test");
446     Cluster cluster = new Cluster(10, 100);
447     for (int i = 0; i < 10; i++) {
448       cluster.startServer();
449     }
450 
451     ArrayList<SimpleClient> clients = new ArrayList<>();
452 
453     // all threads should share the same rpc client
454     AbstractRpcClient rpcClient = createRpcClient(conf, isSyncClient);
455 
456     for (int i = 0; i < 30; i++) {
457       String clientId = "client_" + i + "_";
458       LOG.info("Starting client: " + clientId);
459       SimpleClient client = new SimpleClient(cluster, rpcClient, clientId);
460       client.start();
461       clients.add(client);
462     }
463 
464     LOG.info("Starting MiniChaosMonkey");
465     MiniChaosMonkey cm = new MiniChaosMonkey(cluster);
466     cm.start();
467 
468     Threads.sleep(30000);
469 
470     LOG.info("Stopping MiniChaosMonkey");
471     cm.stopRunning();
472     cm.join();
473     cm.rethrowException();
474 
475     LOG.info("Stopping clients");
476     for (SimpleClient client : clients) {
477       LOG.info("Stopping client: " + client.id);
478       LOG.info(client.id + " numCalls:" + client.numCalls);
479       client.stopRunning();
480       client.join();
481       client.rethrowException();
482       assertTrue(client.numCalls > 10);
483     }
484 
485     LOG.info("Stopping RpcClient");
486     rpcClient.close();
487 
488     LOG.info("Stopping Cluster");
489     cluster.stopRunning();
490   }
491 }