001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.ipc;
019
020import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.SERVICE;
021import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.newBlockingStub;
022import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.newStub;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertNotNull;
026import static org.junit.Assert.assertNull;
027import static org.junit.Assert.assertTrue;
028import static org.junit.Assert.fail;
029import static org.mockito.Matchers.anyObject;
030import static org.mockito.Mockito.spy;
031import static org.mockito.Mockito.verify;
032import static org.mockito.internal.verification.VerificationModeFactory.times;
033
034import java.io.IOException;
035import java.net.InetSocketAddress;
036import java.util.ArrayList;
037import java.util.List;
038
039import org.apache.hadoop.conf.Configuration;
040import org.apache.hadoop.hbase.Cell;
041import org.apache.hadoop.hbase.CellScanner;
042import org.apache.hadoop.hbase.CellUtil;
043import org.apache.hadoop.hbase.DoNotRetryIOException;
044import org.apache.hadoop.hbase.HBaseConfiguration;
045import org.apache.hadoop.hbase.KeyValue;
046import org.apache.hadoop.hbase.Server;
047import org.apache.hadoop.hbase.ipc.RpcServer.BlockingServiceAndInterface;
048import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
049import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos.EchoRequestProto;
050import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos.EchoResponseProto;
051import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos.EmptyRequestProto;
052import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos.EmptyResponseProto;
053import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos.PauseRequestProto;
054import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface;
055import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.Interface;
056import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
057import org.apache.hadoop.hbase.util.Bytes;
058import org.apache.hadoop.io.compress.GzipCodec;
059import org.apache.hadoop.util.StringUtils;
060import org.junit.BeforeClass;
061import org.junit.Test;
062import org.slf4j.Logger;
063import org.slf4j.LoggerFactory;
064import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
065import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
066
067/**
068 * Some basic ipc tests.
069 */
070public abstract class AbstractTestIPC {
071
072  private static final Logger LOG = LoggerFactory.getLogger(AbstractTestIPC.class);
073
074  private static final byte[] CELL_BYTES = Bytes.toBytes("xyz");
075  private static final KeyValue CELL = new KeyValue(CELL_BYTES, CELL_BYTES, CELL_BYTES, CELL_BYTES);
076
077  protected static final Configuration CONF = HBaseConfiguration.create();
078  static {
079    // Set the default to be the old SimpleRpcServer. Subclasses test it and netty.
080    CONF.set(RpcServerFactory.CUSTOM_RPC_SERVER_IMPL_CONF_KEY, SimpleRpcServer.class.getName());
081  }
082
083  protected abstract RpcServer createRpcServer(final Server server, final String name,
084      final List<BlockingServiceAndInterface> services,
085      final InetSocketAddress bindAddress, Configuration conf,
086      RpcScheduler scheduler) throws IOException;
087
088  protected abstract AbstractRpcClient<?> createRpcClientNoCodec(Configuration conf);
089
090  /**
091   * Ensure we do not HAVE TO HAVE a codec.
092   */
093  @Test
094  public void testNoCodec() throws IOException, ServiceException {
095    Configuration conf = HBaseConfiguration.create();
096    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
097        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
098            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
099        new FifoRpcScheduler(CONF, 1));
100    try (AbstractRpcClient<?> client = createRpcClientNoCodec(conf)) {
101      rpcServer.start();
102      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
103      HBaseRpcController pcrc = new HBaseRpcControllerImpl();
104      String message = "hello";
105      assertEquals(message,
106        stub.echo(pcrc, EchoRequestProto.newBuilder().setMessage(message).build()).getMessage());
107      assertNull(pcrc.cellScanner());
108    } finally {
109      rpcServer.stop();
110    }
111  }
112
113  protected abstract AbstractRpcClient<?> createRpcClient(Configuration conf);
114
115  /**
116   * It is hard to verify the compression is actually happening under the wraps. Hope that if
117   * unsupported, we'll get an exception out of some time (meantime, have to trace it manually to
118   * confirm that compression is happening down in the client and server).
119   */
120  @Test
121  public void testCompressCellBlock() throws IOException, ServiceException {
122    Configuration conf = new Configuration(HBaseConfiguration.create());
123    conf.set("hbase.client.rpc.compressor", GzipCodec.class.getCanonicalName());
124    List<Cell> cells = new ArrayList<>();
125    int count = 3;
126    for (int i = 0; i < count; i++) {
127      cells.add(CELL);
128    }
129    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
130        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
131            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
132        new FifoRpcScheduler(CONF, 1));
133
134    try (AbstractRpcClient<?> client = createRpcClient(conf)) {
135      rpcServer.start();
136      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
137      HBaseRpcController pcrc = new HBaseRpcControllerImpl(CellUtil.createCellScanner(cells));
138      String message = "hello";
139      assertEquals(message,
140        stub.echo(pcrc, EchoRequestProto.newBuilder().setMessage(message).build()).getMessage());
141      int index = 0;
142      CellScanner cellScanner = pcrc.cellScanner();
143      assertNotNull(cellScanner);
144      while (cellScanner.advance()) {
145        assertEquals(CELL, cellScanner.current());
146        index++;
147      }
148      assertEquals(count, index);
149    } finally {
150      rpcServer.stop();
151    }
152  }
153
154  protected abstract AbstractRpcClient<?> createRpcClientRTEDuringConnectionSetup(
155      Configuration conf) throws IOException;
156
157  @Test
158  public void testRTEDuringConnectionSetup() throws Exception {
159    Configuration conf = HBaseConfiguration.create();
160    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
161        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
162            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
163        new FifoRpcScheduler(CONF, 1));
164    try (AbstractRpcClient<?> client = createRpcClientRTEDuringConnectionSetup(conf)) {
165      rpcServer.start();
166      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
167      stub.ping(null, EmptyRequestProto.getDefaultInstance());
168      fail("Expected an exception to have been thrown!");
169    } catch (Exception e) {
170      LOG.info("Caught expected exception: " + e.toString());
171      assertTrue(e.toString(), StringUtils.stringifyException(e).contains("Injected fault"));
172    } finally {
173      rpcServer.stop();
174    }
175  }
176
177  /**
178   * Tests that the rpc scheduler is called when requests arrive.
179   */
180  @Test
181  public void testRpcScheduler() throws IOException, ServiceException, InterruptedException {
182    RpcScheduler scheduler = spy(new FifoRpcScheduler(CONF, 1));
183    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
184        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
185            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF, scheduler);
186    verify(scheduler).init((RpcScheduler.Context) anyObject());
187    try (AbstractRpcClient<?> client = createRpcClient(CONF)) {
188      rpcServer.start();
189      verify(scheduler).start();
190      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
191      EchoRequestProto param = EchoRequestProto.newBuilder().setMessage("hello").build();
192      for (int i = 0; i < 10; i++) {
193        stub.echo(null, param);
194      }
195      verify(scheduler, times(10)).dispatch((CallRunner) anyObject());
196    } finally {
197      rpcServer.stop();
198      verify(scheduler).stop();
199    }
200  }
201
202  /** Tests that the rpc scheduler is called when requests arrive. */
203  @Test
204  public void testRpcMaxRequestSize() throws IOException, ServiceException {
205    Configuration conf = new Configuration(CONF);
206    conf.setInt(RpcServer.MAX_REQUEST_SIZE, 1000);
207    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
208        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
209            SERVICE, null)), new InetSocketAddress("localhost", 0), conf,
210        new FifoRpcScheduler(conf, 1));
211    try (AbstractRpcClient<?> client = createRpcClient(conf)) {
212      rpcServer.start();
213      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
214      StringBuilder message = new StringBuilder(1200);
215      for (int i = 0; i < 200; i++) {
216        message.append("hello.");
217      }
218      // set total RPC size bigger than 100 bytes
219      EchoRequestProto param = EchoRequestProto.newBuilder().setMessage(message.toString()).build();
220      stub.echo(
221        new HBaseRpcControllerImpl(CellUtil.createCellScanner(ImmutableList.<Cell> of(CELL))),
222        param);
223      fail("RPC should have failed because it exceeds max request size");
224    } catch (ServiceException e) {
225      LOG.info("Caught expected exception: " + e);
226      assertTrue(e.toString(),
227          StringUtils.stringifyException(e).contains("RequestTooBigException"));
228    } finally {
229      rpcServer.stop();
230    }
231  }
232
233  /**
234   * Tests that the RpcServer creates & dispatches CallRunner object to scheduler with non-null
235   * remoteAddress set to its Call Object
236   * @throws ServiceException
237   */
238  @Test
239  public void testRpcServerForNotNullRemoteAddressInCallObject()
240      throws IOException, ServiceException {
241    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
242        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
243            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
244        new FifoRpcScheduler(CONF, 1));
245    InetSocketAddress localAddr = new InetSocketAddress("localhost", 0);
246    try (AbstractRpcClient<?> client = createRpcClient(CONF)) {
247      rpcServer.start();
248      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
249      assertEquals(localAddr.getAddress().getHostAddress(),
250        stub.addr(null, EmptyRequestProto.getDefaultInstance()).getAddr());
251    } finally {
252      rpcServer.stop();
253    }
254  }
255
256  @Test
257  public void testRemoteError() throws IOException, ServiceException {
258    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
259        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
260            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
261        new FifoRpcScheduler(CONF, 1));
262    try (AbstractRpcClient<?> client = createRpcClient(CONF)) {
263      rpcServer.start();
264      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
265      stub.error(null, EmptyRequestProto.getDefaultInstance());
266    } catch (ServiceException e) {
267      LOG.info("Caught expected exception: " + e);
268      IOException ioe = ProtobufUtil.handleRemoteException(e);
269      assertTrue(ioe instanceof DoNotRetryIOException);
270      assertTrue(ioe.getMessage().contains("server error!"));
271    } finally {
272      rpcServer.stop();
273    }
274  }
275
276  @Test
277  public void testTimeout() throws IOException {
278    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
279        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
280            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
281        new FifoRpcScheduler(CONF, 1));
282    try (AbstractRpcClient<?> client = createRpcClient(CONF)) {
283      rpcServer.start();
284      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
285      HBaseRpcController pcrc = new HBaseRpcControllerImpl();
286      int ms = 1000;
287      int timeout = 100;
288      for (int i = 0; i < 10; i++) {
289        pcrc.reset();
290        pcrc.setCallTimeout(timeout);
291        long startTime = System.nanoTime();
292        try {
293          stub.pause(pcrc, PauseRequestProto.newBuilder().setMs(ms).build());
294        } catch (ServiceException e) {
295          long waitTime = (System.nanoTime() - startTime) / 1000000;
296          // expected
297          LOG.info("Caught expected exception: " + e);
298          IOException ioe = ProtobufUtil.handleRemoteException(e);
299          assertTrue(ioe.getCause() instanceof CallTimeoutException);
300          // confirm that we got exception before the actual pause.
301          assertTrue(waitTime < ms);
302        }
303      }
304    } finally {
305      rpcServer.stop();
306    }
307  }
308
309  protected abstract RpcServer createTestFailingRpcServer(final Server server, final String name,
310      final List<BlockingServiceAndInterface> services,
311      final InetSocketAddress bindAddress, Configuration conf,
312      RpcScheduler scheduler) throws IOException;
313
314  /** Tests that the connection closing is handled by the client with outstanding RPC calls */
315  @Test
316  public void testConnectionCloseWithOutstandingRPCs() throws InterruptedException, IOException {
317    Configuration conf = new Configuration(CONF);
318    RpcServer rpcServer = createTestFailingRpcServer(null, "testRpcServer",
319        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
320            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
321        new FifoRpcScheduler(CONF, 1));
322
323    try (AbstractRpcClient<?> client = createRpcClient(conf)) {
324      rpcServer.start();
325      BlockingInterface stub = newBlockingStub(client, rpcServer.getListenerAddress());
326      EchoRequestProto param = EchoRequestProto.newBuilder().setMessage("hello").build();
327      stub.echo(null, param);
328      fail("RPC should have failed because connection closed");
329    } catch (ServiceException e) {
330      LOG.info("Caught expected exception: " + e.toString());
331    } finally {
332      rpcServer.stop();
333    }
334  }
335
336  @Test
337  public void testAsyncEcho() throws IOException {
338    Configuration conf = HBaseConfiguration.create();
339    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
340        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
341            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
342        new FifoRpcScheduler(CONF, 1));
343    try (AbstractRpcClient<?> client = createRpcClient(conf)) {
344      rpcServer.start();
345      Interface stub = newStub(client, rpcServer.getListenerAddress());
346      int num = 10;
347      List<HBaseRpcController> pcrcList = new ArrayList<>();
348      List<BlockingRpcCallback<EchoResponseProto>> callbackList = new ArrayList<>();
349      for (int i = 0; i < num; i++) {
350        HBaseRpcController pcrc = new HBaseRpcControllerImpl();
351        BlockingRpcCallback<EchoResponseProto> done = new BlockingRpcCallback<>();
352        stub.echo(pcrc, EchoRequestProto.newBuilder().setMessage("hello-" + i).build(), done);
353        pcrcList.add(pcrc);
354        callbackList.add(done);
355      }
356      for (int i = 0; i < num; i++) {
357        HBaseRpcController pcrc = pcrcList.get(i);
358        assertFalse(pcrc.failed());
359        assertNull(pcrc.cellScanner());
360        assertEquals("hello-" + i, callbackList.get(i).get().getMessage());
361      }
362    } finally {
363      rpcServer.stop();
364    }
365  }
366
367  @Test
368  public void testAsyncRemoteError() throws IOException {
369    AbstractRpcClient<?> client = createRpcClient(CONF);
370    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
371        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
372            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
373        new FifoRpcScheduler(CONF, 1));
374    try {
375      rpcServer.start();
376      Interface stub = newStub(client, rpcServer.getListenerAddress());
377      BlockingRpcCallback<EmptyResponseProto> callback = new BlockingRpcCallback<>();
378      HBaseRpcController pcrc = new HBaseRpcControllerImpl();
379      stub.error(pcrc, EmptyRequestProto.getDefaultInstance(), callback);
380      assertNull(callback.get());
381      assertTrue(pcrc.failed());
382      LOG.info("Caught expected exception: " + pcrc.getFailed());
383      IOException ioe = ProtobufUtil.handleRemoteException(pcrc.getFailed());
384      assertTrue(ioe instanceof DoNotRetryIOException);
385      assertTrue(ioe.getMessage().contains("server error!"));
386    } finally {
387      client.close();
388      rpcServer.stop();
389    }
390  }
391
392  @Test
393  public void testAsyncTimeout() throws IOException {
394    RpcServer rpcServer = createRpcServer(null, "testRpcServer",
395        Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(
396            SERVICE, null)), new InetSocketAddress("localhost", 0), CONF,
397        new FifoRpcScheduler(CONF, 1));
398    try (AbstractRpcClient<?> client = createRpcClient(CONF)) {
399      rpcServer.start();
400      Interface stub = newStub(client, rpcServer.getListenerAddress());
401      List<HBaseRpcController> pcrcList = new ArrayList<>();
402      List<BlockingRpcCallback<EmptyResponseProto>> callbackList = new ArrayList<>();
403      int ms = 1000;
404      int timeout = 100;
405      long startTime = System.nanoTime();
406      for (int i = 0; i < 10; i++) {
407        HBaseRpcController pcrc = new HBaseRpcControllerImpl();
408        pcrc.setCallTimeout(timeout);
409        BlockingRpcCallback<EmptyResponseProto> callback = new BlockingRpcCallback<>();
410        stub.pause(pcrc, PauseRequestProto.newBuilder().setMs(ms).build(), callback);
411        pcrcList.add(pcrc);
412        callbackList.add(callback);
413      }
414      for (BlockingRpcCallback<?> callback : callbackList) {
415        assertNull(callback.get());
416      }
417      long waitTime = (System.nanoTime() - startTime) / 1000000;
418      for (HBaseRpcController pcrc : pcrcList) {
419        assertTrue(pcrc.failed());
420        LOG.info("Caught expected exception: " + pcrc.getFailed());
421        IOException ioe = ProtobufUtil.handleRemoteException(pcrc.getFailed());
422        assertTrue(ioe.getCause() instanceof CallTimeoutException);
423      }
424      // confirm that we got exception before the actual pause.
425      assertTrue(waitTime < ms);
426    } finally {
427      rpcServer.stop();
428    }
429  }
430}