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.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  import static org.junit.Assert.fail;
23  import static org.mockito.Matchers.anyObject;
24  import static org.mockito.Mockito.spy;
25  import static org.mockito.Mockito.verify;
26  import static org.mockito.internal.verification.VerificationModeFactory.times;
27  
28  import java.io.IOException;
29  import java.net.ConnectException;
30  import java.net.InetAddress;
31  import java.net.InetSocketAddress;
32  import java.net.SocketTimeoutException;
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.hbase.Cell;
40  import org.apache.hadoop.hbase.CellScanner;
41  import org.apache.hadoop.hbase.CellUtil;
42  import org.apache.hadoop.hbase.HBaseConfiguration;
43  import org.apache.hadoop.hbase.HConstants;
44  import org.apache.hadoop.hbase.KeyValue;
45  import org.apache.hadoop.hbase.ServerName;
46  import org.apache.hadoop.hbase.client.MetricsConnection;
47  import org.apache.hadoop.hbase.exceptions.ConnectionClosingException;
48  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EchoRequestProto;
49  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EchoResponseProto;
50  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EmptyRequestProto;
51  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos.EmptyResponseProto;
52  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
53  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface;
54  import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
55  import org.apache.hadoop.hbase.security.User;
56  import org.apache.hadoop.hbase.util.Bytes;
57  import org.apache.hadoop.hbase.util.Pair;
58  import org.apache.hadoop.io.compress.GzipCodec;
59  import org.apache.hadoop.util.StringUtils;
60  import org.junit.Assert;
61  import org.junit.Test;
62  
63  import com.google.common.collect.ImmutableList;
64  import com.google.common.collect.Lists;
65  import com.google.protobuf.BlockingRpcChannel;
66  import com.google.protobuf.BlockingService;
67  import com.google.protobuf.Descriptors.MethodDescriptor;
68  import com.google.protobuf.Message;
69  import com.google.protobuf.RpcController;
70  import com.google.protobuf.ServiceException;
71  
72  /**
73   * Some basic ipc tests.
74   */
75  public abstract class AbstractTestIPC {
76  
77    private static final Log LOG = LogFactory.getLog(AbstractTestIPC.class);
78  
79    private static byte[] CELL_BYTES = Bytes.toBytes("xyz");
80    private static KeyValue CELL = new KeyValue(CELL_BYTES, CELL_BYTES, CELL_BYTES, CELL_BYTES);
81    static byte[] BIG_CELL_BYTES = new byte[10 * 1024];
82    static KeyValue BIG_CELL = new KeyValue(CELL_BYTES, CELL_BYTES, CELL_BYTES, BIG_CELL_BYTES);
83    static final Configuration CONF = HBaseConfiguration.create();
84    // We are using the test TestRpcServiceProtos generated classes and Service because they are
85    // available and basic with methods like 'echo', and ping. Below we make a blocking service
86    // by passing in implementation of blocking interface. We use this service in all tests that
87    // follow.
88    static final BlockingService SERVICE =
89        TestRpcServiceProtos.TestProtobufRpcProto
90            .newReflectiveBlockingService(new TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface() {
91  
92              @Override
93              public EmptyResponseProto ping(RpcController controller, EmptyRequestProto request)
94                  throws ServiceException {
95                return null;
96              }
97  
98              @Override
99              public EmptyResponseProto error(RpcController controller, EmptyRequestProto request)
100                 throws ServiceException {
101               return null;
102             }
103 
104             @Override
105             public EchoResponseProto echo(RpcController controller, EchoRequestProto request)
106                 throws ServiceException {
107               if (controller instanceof PayloadCarryingRpcController) {
108                 PayloadCarryingRpcController pcrc = (PayloadCarryingRpcController) controller;
109                 // If cells, scan them to check we are able to iterate what we were given and since
110                 // this is
111                 // an echo, just put them back on the controller creating a new block. Tests our
112                 // block
113                 // building.
114                 CellScanner cellScanner = pcrc.cellScanner();
115                 List<Cell> list = null;
116                 if (cellScanner != null) {
117                   list = new ArrayList<Cell>();
118                   try {
119                     while (cellScanner.advance()) {
120                       list.add(cellScanner.current());
121                     }
122                   } catch (IOException e) {
123                     throw new ServiceException(e);
124                   }
125                 }
126                 cellScanner = CellUtil.createCellScanner(list);
127                 ((PayloadCarryingRpcController) controller).setCellScanner(cellScanner);
128               }
129               return EchoResponseProto.newBuilder().setMessage(request.getMessage()).build();
130             }
131           });
132 
133   /**
134    * Instance of server. We actually don't do anything speical in here so could just use
135    * HBaseRpcServer directly.
136    */
137   static class TestRpcServer extends RpcServer {
138 
139     TestRpcServer() throws IOException {
140       this(new FifoRpcScheduler(CONF, 1), CONF);
141     }
142 
143     TestRpcServer(Configuration conf) throws IOException {
144       this(new FifoRpcScheduler(conf, 1), conf);
145     }
146 
147     TestRpcServer(RpcScheduler scheduler, Configuration conf) throws IOException {
148       super(null, "testRpcServer", Lists
149           .newArrayList(new BlockingServiceAndInterface(SERVICE, null)), new InetSocketAddress(
150           "localhost", 0), conf, scheduler);
151     }
152 
153     @Override
154     public Pair<Message, CellScanner> call(BlockingService service, MethodDescriptor md,
155         Message param, CellScanner cellScanner, long receiveTime, MonitoredRPCHandler status)
156         throws IOException {
157       return super.call(service, md, param, cellScanner, receiveTime, status);
158     }
159   }
160 
161   protected abstract AbstractRpcClient createRpcClientNoCodec(Configuration conf);
162 
163   /**
164    * Ensure we do not HAVE TO HAVE a codec.
165    * @throws InterruptedException
166    * @throws IOException
167    */
168   @Test
169   public void testNoCodec() throws InterruptedException, IOException {
170     Configuration conf = HBaseConfiguration.create();
171     AbstractRpcClient client = createRpcClientNoCodec(conf);
172     TestRpcServer rpcServer = new TestRpcServer();
173     try {
174       rpcServer.start();
175       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
176       final String message = "hello";
177       EchoRequestProto param = EchoRequestProto.newBuilder().setMessage(message).build();
178       InetSocketAddress address = rpcServer.getListenerAddress();
179       if (address == null) {
180         throw new IOException("Listener channel is closed");
181       }
182       Pair<Message, CellScanner> r =
183           client.call(null, md, param, md.getOutputType().toProto(), User.getCurrent(), address,
184               new MetricsConnection.CallStats());
185       assertTrue(r.getSecond() == null);
186       // Silly assertion that the message is in the returned pb.
187       assertTrue(r.getFirst().toString().contains(message));
188     } finally {
189       client.close();
190       rpcServer.stop();
191     }
192   }
193 
194   protected abstract AbstractRpcClient createRpcClient(Configuration conf);
195 
196   /**
197    * It is hard to verify the compression is actually happening under the wraps. Hope that if
198    * unsupported, we'll get an exception out of some time (meantime, have to trace it manually to
199    * confirm that compression is happening down in the client and server).
200    * @throws IOException
201    * @throws InterruptedException
202    * @throws SecurityException
203    * @throws NoSuchMethodException
204    */
205   @Test
206   public void testCompressCellBlock() throws IOException, InterruptedException, SecurityException,
207       NoSuchMethodException, ServiceException {
208     Configuration conf = new Configuration(HBaseConfiguration.create());
209     conf.set("hbase.client.rpc.compressor", GzipCodec.class.getCanonicalName());
210     List<Cell> cells = new ArrayList<Cell>();
211     int count = 3;
212     for (int i = 0; i < count; i++) {
213       cells.add(CELL);
214     }
215     AbstractRpcClient client = createRpcClient(conf);
216     TestRpcServer rpcServer = new TestRpcServer();
217     try {
218       rpcServer.start();
219       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
220       EchoRequestProto param = EchoRequestProto.newBuilder().setMessage("hello").build();
221       PayloadCarryingRpcController pcrc =
222           new PayloadCarryingRpcController(CellUtil.createCellScanner(cells));
223       InetSocketAddress address = rpcServer.getListenerAddress();
224       if (address == null) {
225         throw new IOException("Listener channel is closed");
226       }
227       Pair<Message, CellScanner> r =
228           client.call(pcrc, md, param, md.getOutputType().toProto(), User.getCurrent(), address,
229               new MetricsConnection.CallStats());
230       int index = 0;
231       while (r.getSecond().advance()) {
232         assertTrue(CELL.equals(r.getSecond().current()));
233         index++;
234       }
235       assertEquals(count, index);
236     } finally {
237       client.close();
238       rpcServer.stop();
239     }
240   }
241 
242   protected abstract AbstractRpcClient createRpcClientRTEDuringConnectionSetup(Configuration conf)
243       throws IOException;
244 
245   @Test
246   public void testRTEDuringConnectionSetup() throws Exception {
247     Configuration conf = HBaseConfiguration.create();
248     TestRpcServer rpcServer = new TestRpcServer();
249     AbstractRpcClient client = createRpcClientRTEDuringConnectionSetup(conf);
250     try {
251       rpcServer.start();
252       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
253       EchoRequestProto param = EchoRequestProto.newBuilder().setMessage("hello").build();
254       InetSocketAddress address = rpcServer.getListenerAddress();
255       if (address == null) {
256         throw new IOException("Listener channel is closed");
257       }
258       client.call(null, md, param, null, User.getCurrent(), address,
259           new MetricsConnection.CallStats());
260       fail("Expected an exception to have been thrown!");
261     } catch (Exception e) {
262       LOG.info("Caught expected exception: " + e.toString());
263       assertTrue(StringUtils.stringifyException(e).contains("Injected fault"));
264     } finally {
265       client.close();
266       rpcServer.stop();
267     }
268   }
269 
270   /** Tests that the rpc scheduler is called when requests arrive. */
271   @Test
272   public void testRpcScheduler() throws IOException, InterruptedException {
273     RpcScheduler scheduler = spy(new FifoRpcScheduler(CONF, 1));
274     RpcServer rpcServer = new TestRpcServer(scheduler, CONF);
275     verify(scheduler).init((RpcScheduler.Context) anyObject());
276     AbstractRpcClient client = createRpcClient(CONF);
277     try {
278       rpcServer.start();
279       verify(scheduler).start();
280       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
281       EchoRequestProto param = EchoRequestProto.newBuilder().setMessage("hello").build();
282       InetSocketAddress address = rpcServer.getListenerAddress();
283       if (address == null) {
284         throw new IOException("Listener channel is closed");
285       }
286       for (int i = 0; i < 10; i++) {
287         client.call(new PayloadCarryingRpcController(
288             CellUtil.createCellScanner(ImmutableList.<Cell> of(CELL))), md, param,
289             md.getOutputType().toProto(), User.getCurrent(), address,
290             new MetricsConnection.CallStats());
291       }
292       verify(scheduler, times(10)).dispatch((CallRunner) anyObject());
293     } finally {
294       rpcServer.stop();
295       verify(scheduler).stop();
296     }
297   }
298 
299   /** Tests that the rpc scheduler is called when requests arrive. */
300   @Test
301   public void testRpcMaxRequestSize() throws IOException, InterruptedException {
302     Configuration conf = new Configuration(CONF);
303     conf.setInt(RpcServer.MAX_REQUEST_SIZE, 1000);
304     RpcServer rpcServer = new TestRpcServer(conf);
305     AbstractRpcClient client = createRpcClient(conf);
306     try {
307       rpcServer.start();
308       MethodDescriptor md = SERVICE.getDescriptorForType().findMethodByName("echo");
309       StringBuilder message = new StringBuilder(1200);
310       for (int i = 0; i < 200; i++) {
311         message.append("hello.");
312       }
313       // set total RPC size bigger than 1000 bytes
314       EchoRequestProto param = EchoRequestProto.newBuilder().setMessage(message.toString()).build();
315       InetSocketAddress address = rpcServer.getListenerAddress();
316       if (address == null) {
317         throw new IOException("Listener channel is closed");
318       }
319       try {
320         client.call(new PayloadCarryingRpcController(
321           CellUtil.createCellScanner(ImmutableList.<Cell> of(CELL))), md, param,
322           md.getOutputType().toProto(), User.getCurrent(), address,
323           new MetricsConnection.CallStats());
324         fail("RPC should have failed because it exceeds max request size");
325       } catch(IOException e) {
326         LOG.info("Caught expected exception: " + e);
327         assertTrue(e.toString(),
328             StringUtils.stringifyException(e).contains("RequestTooBigException"));
329       }
330     } finally {
331       rpcServer.stop();
332     }
333   }
334 
335   /**
336    * Instance of RpcServer that echoes client hostAddress back to client
337    */
338   static class TestRpcServer1 extends RpcServer {
339 
340     private static BlockingInterface SERVICE1 =
341         new TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface() {
342           @Override
343           public EmptyResponseProto ping(RpcController unused, EmptyRequestProto request)
344               throws ServiceException {
345             return EmptyResponseProto.newBuilder().build();
346           }
347 
348           @Override
349           public EchoResponseProto echo(RpcController unused, EchoRequestProto request)
350               throws ServiceException {
351             final InetAddress remoteAddr = TestRpcServer1.getRemoteAddress();
352             final String message = remoteAddr == null ? "NULL" : remoteAddr.getHostAddress();
353             return EchoResponseProto.newBuilder().setMessage(message).build();
354           }
355 
356           @Override
357           public EmptyResponseProto error(RpcController unused, EmptyRequestProto request)
358               throws ServiceException {
359             throw new ServiceException("error", new IOException("error"));
360           }
361         };
362 
363     TestRpcServer1() throws IOException {
364       this(new FifoRpcScheduler(CONF, 1));
365     }
366 
367     TestRpcServer1(RpcScheduler scheduler) throws IOException {
368       super(null, "testRemoteAddressInCallObject", Lists
369           .newArrayList(new BlockingServiceAndInterface(TestRpcServiceProtos.TestProtobufRpcProto
370               .newReflectiveBlockingService(SERVICE1), null)),
371           new InetSocketAddress("localhost", 0), CONF, scheduler);
372     }
373   }
374 
375   /**
376    * Tests that the RpcServer creates & dispatches CallRunner object to scheduler with non-null
377    * remoteAddress set to its Call Object
378    * @throws ServiceException
379    */
380   @Test
381   public void testRpcServerForNotNullRemoteAddressInCallObject() throws IOException,
382       ServiceException {
383     final RpcScheduler scheduler = new FifoRpcScheduler(CONF, 1);
384     final TestRpcServer1 rpcServer = new TestRpcServer1(scheduler);
385     final InetSocketAddress localAddr = new InetSocketAddress("localhost", 0);
386     final AbstractRpcClient client =
387         new RpcClientImpl(CONF, HConstants.CLUSTER_ID_DEFAULT, localAddr, null);
388     try {
389       rpcServer.start();
390       final InetSocketAddress isa = rpcServer.getListenerAddress();
391       if (isa == null) {
392         throw new IOException("Listener channel is closed");
393       }
394       final BlockingRpcChannel channel =
395           client.createBlockingRpcChannel(
396             ServerName.valueOf(isa.getHostName(), isa.getPort(), System.currentTimeMillis()),
397             User.getCurrent(), 0);
398       TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub =
399           TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(channel);
400       final EchoRequestProto echoRequest =
401           EchoRequestProto.newBuilder().setMessage("GetRemoteAddress").build();
402       final EchoResponseProto echoResponse = stub.echo(null, echoRequest);
403       Assert.assertEquals(localAddr.getAddress().getHostAddress(), echoResponse.getMessage());
404     } finally {
405       client.close();
406       rpcServer.stop();
407     }
408   }
409 
410   @Test
411   public void testWrapException() throws Exception {
412     AbstractRpcClient client =
413         (AbstractRpcClient) RpcClientFactory.createClient(CONF, "AbstractTestIPC");
414     final InetSocketAddress address = InetSocketAddress.createUnresolved("localhost", 0);
415     assertTrue(client.wrapException(address, new ConnectException()) instanceof ConnectException);
416     assertTrue(client.wrapException(address,
417       new SocketTimeoutException()) instanceof SocketTimeoutException);
418     assertTrue(client.wrapException(address, new ConnectionClosingException(
419         "Test AbstractRpcClient#wrapException")) instanceof ConnectionClosingException);
420     assertTrue(client
421         .wrapException(address, new CallTimeoutException("Test AbstractRpcClient#wrapException"))
422         .getCause() instanceof CallTimeoutException);
423   }
424 }