View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertNull;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.io.IOException;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.hbase.Coprocessor;
32  import org.apache.hadoop.hbase.CoprocessorEnvironment;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.HRegionLocation;
37  import org.apache.hadoop.hbase.testclassification.MediumTests;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.ServerName;
40  import org.apache.hadoop.hbase.client.HTable;
41  import org.apache.hadoop.hbase.client.Put;
42  import org.apache.hadoop.hbase.client.RegionLocator;
43  import org.apache.hadoop.hbase.client.Table;
44  import org.apache.hadoop.hbase.client.coprocessor.Batch;
45  import org.apache.hadoop.hbase.coprocessor.CoprocessorException;
46  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
47  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
48  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
49  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos;
50  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.CountRequest;
51  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.CountResponse;
52  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.HelloRequest;
53  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.HelloResponse;
54  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.IncrementCountRequest;
55  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.IncrementCountResponse;
56  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.NoopRequest;
57  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.NoopResponse;
58  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.PingRequest;
59  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.PingProtos.PingResponse;
60  import org.apache.hadoop.hbase.ipc.BlockingRpcCallback;
61  import org.apache.hadoop.hbase.util.Bytes;
62  import org.junit.After;
63  import org.junit.AfterClass;
64  import org.junit.Before;
65  import org.junit.BeforeClass;
66  import org.junit.Test;
67  import org.junit.experimental.categories.Category;
68  
69  import com.google.protobuf.RpcCallback;
70  import com.google.protobuf.RpcController;
71  import com.google.protobuf.Service;
72  import com.google.protobuf.ServiceException;
73  
74  @Category(MediumTests.class)
75  public class TestServerCustomProtocol {
76    private static final Log LOG = LogFactory.getLog(TestServerCustomProtocol.class);
77    static final String WHOAREYOU = "Who are you?";
78    static final String NOBODY = "nobody";
79    static final String HELLO = "Hello, ";
80  
81    /* Test protocol implementation */
82    public static class PingHandler extends PingProtos.PingService
83    implements Coprocessor, CoprocessorService {
84      private int counter = 0;
85  
86      @Override
87      public void start(CoprocessorEnvironment env) throws IOException {
88        if (env instanceof RegionCoprocessorEnvironment) return;
89        throw new CoprocessorException("Must be loaded on a table region!");
90      }
91  
92      @Override
93      public void stop(CoprocessorEnvironment env) throws IOException {
94        // Nothing to do.
95      }
96  
97      @Override
98      public void ping(RpcController controller, PingRequest request,
99          RpcCallback<PingResponse> done) {
100       this.counter++;
101       done.run(PingResponse.newBuilder().setPong("pong").build());
102     }
103 
104     @Override
105     public void count(RpcController controller, CountRequest request,
106         RpcCallback<CountResponse> done) {
107       done.run(CountResponse.newBuilder().setCount(this.counter).build());
108     }
109 
110     @Override
111     public void increment(RpcController controller,
112         IncrementCountRequest request, RpcCallback<IncrementCountResponse> done) {
113       this.counter += request.getDiff();
114       done.run(IncrementCountResponse.newBuilder().setCount(this.counter).build());
115     }
116 
117     @Override
118     public void hello(RpcController controller, HelloRequest request,
119         RpcCallback<HelloResponse> done) {
120       if (!request.hasName()) done.run(HelloResponse.newBuilder().setResponse(WHOAREYOU).build());
121       else if (request.getName().equals(NOBODY)) done.run(HelloResponse.newBuilder().build());
122       else done.run(HelloResponse.newBuilder().setResponse(HELLO + request.getName()).build());
123     }
124 
125     @Override
126     public void noop(RpcController controller, NoopRequest request,
127         RpcCallback<NoopResponse> done) {
128       done.run(NoopResponse.newBuilder().build());
129     }
130 
131     @Override
132     public Service getService() {
133       return this;
134     }
135   }
136 
137   private static final TableName TEST_TABLE = TableName.valueOf("test");
138   private static final byte[] TEST_FAMILY = Bytes.toBytes("f1");
139 
140   private static final byte[] ROW_A = Bytes.toBytes("aaa");
141   private static final byte[] ROW_B = Bytes.toBytes("bbb");
142   private static final byte[] ROW_C = Bytes.toBytes("ccc");
143 
144   private static final byte[] ROW_AB = Bytes.toBytes("abb");
145   private static final byte[] ROW_BC = Bytes.toBytes("bcc");
146 
147   private static HBaseTestingUtility util = new HBaseTestingUtility();
148 
149   @BeforeClass
150   public static void setupBeforeClass() throws Exception {
151     util.getConfiguration().set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
152       PingHandler.class.getName());
153     util.startMiniCluster();
154   }
155 
156   @Before
157   public void before()  throws Exception {
158     HTable table = util.createTable(TEST_TABLE, TEST_FAMILY);
159     util.createMultiRegions(util.getConfiguration(), table, TEST_FAMILY,
160       new byte[][]{ HConstants.EMPTY_BYTE_ARRAY, ROW_B, ROW_C});
161 
162     Put puta = new Put( ROW_A );
163     puta.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
164     table.put(puta);
165 
166     Put putb = new Put( ROW_B );
167     putb.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
168     table.put(putb);
169 
170     Put putc = new Put( ROW_C );
171     putc.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
172     table.put(putc);
173   }
174 
175   @After
176   public void after() throws Exception {
177     util.deleteTable(TEST_TABLE);
178   }
179 
180   @AfterClass
181   public static void tearDownAfterClass() throws Exception {
182     util.shutdownMiniCluster();
183   }
184 
185   @Test
186   public void testSingleProxy() throws Throwable {
187     Table table = new HTable(util.getConfiguration(), TEST_TABLE);
188     Map<byte [], String> results = ping(table, null, null);
189     // There are three regions so should get back three results.
190     assertEquals(3, results.size());
191     for (Map.Entry<byte [], String> e: results.entrySet()) {
192       assertEquals("Invalid custom protocol response", "pong", e.getValue());
193     }
194     hello(table, "George", HELLO + "George");
195     LOG.info("Did george");
196     hello(table, null, "Who are you?");
197     LOG.info("Who are you");
198     hello(table, NOBODY, null);
199     LOG.info(NOBODY);
200     Map<byte [], Integer> intResults = table.coprocessorService(PingProtos.PingService.class,
201       null, null,
202       new Batch.Call<PingProtos.PingService, Integer>() {
203         @Override
204         public Integer call(PingProtos.PingService instance) throws IOException {
205           BlockingRpcCallback<PingProtos.CountResponse> rpcCallback =
206             new BlockingRpcCallback<PingProtos.CountResponse>();
207           instance.count(null, PingProtos.CountRequest.newBuilder().build(), rpcCallback);
208           return rpcCallback.get().getCount();
209         }
210       });
211     int count = -1;
212     for (Map.Entry<byte [], Integer> e: intResults.entrySet()) {
213       assertTrue(e.getValue() > 0);
214       count = e.getValue();
215     }
216     final int diff = 5;
217     intResults = table.coprocessorService(PingProtos.PingService.class,
218       null, null,
219       new Batch.Call<PingProtos.PingService, Integer>() {
220         @Override
221         public Integer call(PingProtos.PingService instance) throws IOException {
222           BlockingRpcCallback<PingProtos.IncrementCountResponse> rpcCallback =
223             new BlockingRpcCallback<PingProtos.IncrementCountResponse>();
224           instance.increment(null, PingProtos.IncrementCountRequest.newBuilder().setDiff(diff).build(),
225             rpcCallback);
226           return rpcCallback.get().getCount();
227         }
228       });
229     // There are three regions so should get back three results.
230     assertEquals(3, results.size());
231     for (Map.Entry<byte [], Integer> e: intResults.entrySet()) {
232       assertEquals(e.getValue().intValue(), count + diff);
233     }
234     table.close();
235   }
236 
237   private Map<byte [], String> hello(final Table table, final String send, final String response)
238   throws ServiceException, Throwable {
239     Map<byte [], String> results = hello(table, send);
240     for (Map.Entry<byte [], String> e: results.entrySet()) {
241       assertEquals("Invalid custom protocol response", response, e.getValue());
242     }
243     return results;
244   }
245 
246   private Map<byte [], String> hello(final Table table, final String send)
247   throws ServiceException, Throwable {
248     return hello(table, send, null, null);
249   }
250 
251   private Map<byte [], String> hello(final Table table, final String send, final byte [] start,
252       final byte [] end)
253   throws ServiceException, Throwable {
254     return table.coprocessorService(PingProtos.PingService.class,
255         start, end,
256         new Batch.Call<PingProtos.PingService, String>() {
257           @Override
258           public String call(PingProtos.PingService instance) throws IOException {
259             BlockingRpcCallback<PingProtos.HelloResponse> rpcCallback =
260               new BlockingRpcCallback<PingProtos.HelloResponse>();
261             PingProtos.HelloRequest.Builder builder = PingProtos.HelloRequest.newBuilder();
262             if (send != null) builder.setName(send);
263             instance.hello(null, builder.build(), rpcCallback);
264             PingProtos.HelloResponse r = rpcCallback.get();
265             return r != null && r.hasResponse()? r.getResponse(): null;
266           }
267         });
268   }
269 
270   private Map<byte [], String> compoundOfHelloAndPing(final Table table, final byte [] start,
271       final byte [] end)
272   throws ServiceException, Throwable {
273     return table.coprocessorService(PingProtos.PingService.class,
274         start, end,
275         new Batch.Call<PingProtos.PingService, String>() {
276           @Override
277           public String call(PingProtos.PingService instance) throws IOException {
278             BlockingRpcCallback<PingProtos.HelloResponse> rpcCallback =
279               new BlockingRpcCallback<PingProtos.HelloResponse>();
280             PingProtos.HelloRequest.Builder builder = PingProtos.HelloRequest.newBuilder();
281             // Call ping on same instance.  Use result calling hello on same instance.
282             builder.setName(doPing(instance));
283             instance.hello(null, builder.build(), rpcCallback);
284             PingProtos.HelloResponse r = rpcCallback.get();
285             return r != null && r.hasResponse()? r.getResponse(): null;
286           }
287         });
288   }
289 
290   private Map<byte [], String> noop(final Table table, final byte [] start,
291       final byte [] end)
292   throws ServiceException, Throwable {
293     return table.coprocessorService(PingProtos.PingService.class, start, end,
294         new Batch.Call<PingProtos.PingService, String>() {
295           @Override
296           public String call(PingProtos.PingService instance) throws IOException {
297             BlockingRpcCallback<PingProtos.NoopResponse> rpcCallback =
298               new BlockingRpcCallback<PingProtos.NoopResponse>();
299             PingProtos.NoopRequest.Builder builder = PingProtos.NoopRequest.newBuilder();
300             instance.noop(null, builder.build(), rpcCallback);
301             rpcCallback.get();
302             // Looks like null is expected when void.  That is what the test below is looking for
303             return null;
304           }
305         });
306   }
307 
308   @Test
309   public void testSingleMethod() throws Throwable {
310     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
311     Map<byte [], String> results = table.coprocessorService(PingProtos.PingService.class,
312       null, ROW_A,
313       new Batch.Call<PingProtos.PingService, String>() {
314         @Override
315         public String call(PingProtos.PingService instance) throws IOException {
316           BlockingRpcCallback<PingProtos.PingResponse> rpcCallback =
317             new BlockingRpcCallback<PingProtos.PingResponse>();
318           instance.ping(null, PingProtos.PingRequest.newBuilder().build(), rpcCallback);
319           return rpcCallback.get().getPong();
320         }
321       });
322     // Should have gotten results for 1 of the three regions only since we specified
323     // rows from 1 region
324     assertEquals(1, results.size());
325     verifyRegionResults(table, results, ROW_A);
326 
327     final String name = "NAME";
328     results = hello(table, name, null, ROW_A);
329     // Should have gotten results for 1 of the three regions only since we specified
330     // rows from 1 region
331     assertEquals(1, results.size());
332     verifyRegionResults(table, results, "Hello, NAME", ROW_A);
333     table.close();
334   }
335 
336   @Test
337   public void testRowRange() throws Throwable {
338     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
339     for (Entry<HRegionInfo, ServerName> e: table.getRegionLocations().entrySet()) {
340       LOG.info("Region " + e.getKey().getRegionNameAsString() + ", servername=" + e.getValue());
341     }
342     // Here are what regions looked like on a run:
343     //
344     // test,,1355943549657.c65d4822d8bdecc033a96451f3a0f55d.
345     // test,bbb,1355943549661.110393b070dd1ed93441e0bc9b3ffb7e.
346     // test,ccc,1355943549665.c3d6d125141359cbbd2a43eaff3cdf74.
347 
348     Map<byte [], String> results = ping(table, null, ROW_A);
349     // Should contain first region only.
350     assertEquals(1, results.size());
351     verifyRegionResults(table, results, ROW_A);
352 
353     // Test start row + empty end
354     results = ping(table, ROW_BC, null);
355     assertEquals(2, results.size());
356     // should contain last 2 regions
357     HRegionLocation loc = table.getRegionLocation(ROW_A, true);
358     assertNull("Should be missing region for row aaa (prior to start row)",
359       results.get(loc.getRegionInfo().getRegionName()));
360     verifyRegionResults(table, results, ROW_B);
361     verifyRegionResults(table, results, ROW_C);
362 
363     // test empty start + end
364     results = ping(table, null, ROW_BC);
365     // should contain the first 2 regions
366     assertEquals(2, results.size());
367     verifyRegionResults(table, results, ROW_A);
368     verifyRegionResults(table, results, ROW_B);
369     loc = table.getRegionLocation(ROW_C, true);
370     assertNull("Should be missing region for row ccc (past stop row)",
371         results.get(loc.getRegionInfo().getRegionName()));
372 
373     // test explicit start + end
374     results = ping(table, ROW_AB, ROW_BC);
375     // should contain first 2 regions
376     assertEquals(2, results.size());
377     verifyRegionResults(table, results, ROW_A);
378     verifyRegionResults(table, results, ROW_B);
379     loc = table.getRegionLocation(ROW_C, true);
380     assertNull("Should be missing region for row ccc (past stop row)",
381         results.get(loc.getRegionInfo().getRegionName()));
382 
383     // test single region
384     results = ping(table, ROW_B, ROW_BC);
385     // should only contain region bbb
386     assertEquals(1, results.size());
387     verifyRegionResults(table, results, ROW_B);
388     loc = table.getRegionLocation(ROW_A, true);
389     assertNull("Should be missing region for row aaa (prior to start)",
390         results.get(loc.getRegionInfo().getRegionName()));
391     loc = table.getRegionLocation(ROW_C, true);
392     assertNull("Should be missing region for row ccc (past stop row)",
393         results.get(loc.getRegionInfo().getRegionName()));
394     table.close();
395   }
396 
397   private Map<byte [], String> ping(final Table table, final byte [] start, final byte [] end)
398   throws ServiceException, Throwable {
399     return table.coprocessorService(PingProtos.PingService.class, start, end,
400       new Batch.Call<PingProtos.PingService, String>() {
401         @Override
402         public String call(PingProtos.PingService instance) throws IOException {
403           return doPing(instance);
404         }
405       });
406   }
407 
408   private static String doPing(PingProtos.PingService instance) throws IOException {
409     BlockingRpcCallback<PingProtos.PingResponse> rpcCallback =
410         new BlockingRpcCallback<PingProtos.PingResponse>();
411       instance.ping(null, PingProtos.PingRequest.newBuilder().build(), rpcCallback);
412       return rpcCallback.get().getPong();
413   }
414 
415   @Test
416   public void testCompoundCall() throws Throwable {
417     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
418     Map<byte [], String> results = compoundOfHelloAndPing(table, ROW_A, ROW_C);
419     verifyRegionResults(table, results, "Hello, pong", ROW_A);
420     verifyRegionResults(table, results, "Hello, pong", ROW_B);
421     verifyRegionResults(table, results, "Hello, pong", ROW_C);
422     table.close();
423   }
424 
425   @Test
426   public void testNullCall() throws Throwable {
427     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
428     Map<byte[],String> results = hello(table, null, ROW_A, ROW_C);
429     verifyRegionResults(table, results, "Who are you?", ROW_A);
430     verifyRegionResults(table, results, "Who are you?", ROW_B);
431     verifyRegionResults(table, results, "Who are you?", ROW_C);
432   }
433 
434   @Test
435   public void testNullReturn() throws Throwable {
436     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
437     Map<byte[],String> results = hello(table, "nobody", ROW_A, ROW_C);
438     verifyRegionResults(table, results, null, ROW_A);
439     verifyRegionResults(table, results, null, ROW_B);
440     verifyRegionResults(table, results, null, ROW_C);
441   }
442 
443   @Test
444   public void testEmptyReturnType() throws Throwable {
445     Table table = new HTable(util.getConfiguration(), TEST_TABLE);
446     Map<byte[],String> results = noop(table, ROW_A, ROW_C);
447     assertEquals("Should have results from three regions", 3, results.size());
448     // all results should be null
449     for (Object v : results.values()) {
450       assertNull(v);
451     }
452   }
453 
454   private void verifyRegionResults(RegionLocator table,
455       Map<byte[],String> results, byte[] row) throws Exception {
456     verifyRegionResults(table, results, "pong", row);
457   }
458 
459   private void verifyRegionResults(RegionLocator table,
460       Map<byte[], String> results, String expected, byte[] row)
461   throws Exception {
462     for (Map.Entry<byte [], String> e: results.entrySet()) {
463       LOG.info("row=" + Bytes.toString(row) + ", expected=" + expected +
464        ", result key=" + Bytes.toString(e.getKey()) +
465        ", value=" + e.getValue());
466     }
467     HRegionLocation loc = table.getRegionLocation(row, true);
468     byte[] region = loc.getRegionInfo().getRegionName();
469     assertTrue("Results should contain region " +
470       Bytes.toStringBinary(region) + " for row '" + Bytes.toStringBinary(row)+ "'",
471       results.containsKey(region));
472     assertEquals("Invalid result for row '"+Bytes.toStringBinary(row)+"'",
473       expected, results.get(region));
474   }
475 }