1   /*
2    * Copyright 2011 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import static org.junit.Assert.*;
23  
24  import java.io.IOException;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.hadoop.hbase.*;
29  import org.apache.hadoop.hbase.client.Get;
30  import org.apache.hadoop.hbase.client.HTable;
31  import org.apache.hadoop.hbase.client.Put;
32  import org.apache.hadoop.hbase.client.Row;
33  import org.apache.hadoop.hbase.client.coprocessor.Batch;
34  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
35  import org.apache.hadoop.hbase.ipc.CoprocessorProtocol;
36  import org.apache.hadoop.hbase.ipc.HMasterInterface;
37  import org.apache.hadoop.hbase.ipc.HMasterRegionInterface;
38  import org.apache.hadoop.hbase.ipc.ProtocolSignature;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.JVMClusterUtil;
41  import org.apache.hadoop.hbase.ipc.VersionedProtocol;
42  import org.junit.AfterClass;
43  import org.junit.BeforeClass;
44  import org.junit.Test;
45  
46  import com.google.common.collect.Lists;
47  import org.junit.experimental.categories.Category;
48  
49  @Category(MediumTests.class)
50  public class TestServerCustomProtocol {
51    /* Test protocol */
52    public static interface PingProtocol extends CoprocessorProtocol {
53      public String ping();
54      public int getPingCount();
55      public int incrementCount(int diff);
56      public String hello(String name);
57      public void noop();
58    }
59  
60    /* Test protocol implementation */
61    public static class PingHandler implements Coprocessor, PingProtocol, VersionedProtocol {
62      static long VERSION = 1;
63      private int counter = 0;
64      @Override
65      public String ping() {
66        counter++;
67        return "pong";
68      }
69  
70      @Override
71      public int getPingCount() {
72        return counter;
73      }
74  
75      @Override
76      public int incrementCount(int diff) {
77        counter += diff;
78        return counter;
79      }
80  
81      @Override
82      public String hello(String name) {
83        if (name == null) {
84          return "Who are you?";
85        } else if ("nobody".equals(name)) {
86          return null;
87        }
88        return "Hello, "+name;
89      }
90  
91      @Override
92      public void noop() {
93        // do nothing, just test void return type
94      }
95  
96      @Override
97      public ProtocolSignature getProtocolSignature(
98          String protocol, long version, int clientMethodsHashCode)
99      throws IOException {
100       return new ProtocolSignature(VERSION, null);
101     }
102 
103     @Override
104     public long getProtocolVersion(String s, long l) throws IOException {
105       return VERSION;
106     }
107 
108     @Override
109     public void start(CoprocessorEnvironment env) throws IOException {
110     }
111 
112     @Override
113     public void stop(CoprocessorEnvironment env) throws IOException {
114     }
115   }
116 
117   private static final byte[] TEST_TABLE = Bytes.toBytes("test");
118   private static final byte[] TEST_FAMILY = Bytes.toBytes("f1");
119 
120   private static final byte[] ROW_A = Bytes.toBytes("aaa");
121   private static final byte[] ROW_B = Bytes.toBytes("bbb");
122   private static final byte[] ROW_C = Bytes.toBytes("ccc");
123 
124   private static final byte[] ROW_AB = Bytes.toBytes("abb");
125   private static final byte[] ROW_BC = Bytes.toBytes("bcc");
126 
127   private static HBaseTestingUtility util = new HBaseTestingUtility();
128   private static MiniHBaseCluster cluster = null;
129 
130   @BeforeClass
131   public static void setupBeforeClass() throws Exception {
132     util.getConfiguration().set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
133         PingHandler.class.getName());
134     util.startMiniCluster(1);
135     cluster = util.getMiniHBaseCluster();
136 
137     HTable table = util.createTable(TEST_TABLE, TEST_FAMILY);
138     util.createMultiRegions(util.getConfiguration(), table, TEST_FAMILY,
139         new byte[][]{ HConstants.EMPTY_BYTE_ARRAY,
140             ROW_B, ROW_C});
141 
142     Put puta = new Put( ROW_A );
143     puta.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
144     table.put(puta);
145 
146     Put putb = new Put( ROW_B );
147     putb.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
148     table.put(putb);
149 
150     Put putc = new Put( ROW_C );
151     putc.add(TEST_FAMILY, Bytes.toBytes("col1"), Bytes.toBytes(1));
152     table.put(putc);
153   }
154 
155   @AfterClass
156   public static void tearDownAfterClass() throws Exception {
157     util.shutdownMiniCluster();
158   }
159 
160   @Test
161   public void testSingleProxy() throws Exception {
162     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
163 
164     PingProtocol pinger = table.coprocessorProxy(PingProtocol.class, ROW_A);
165     String result = pinger.ping();
166     assertEquals("Invalid custom protocol response", "pong", result);
167     result = pinger.hello("George");
168     assertEquals("Invalid custom protocol response", "Hello, George", result);
169     result = pinger.hello(null);
170     assertEquals("Should handle NULL parameter", "Who are you?", result);
171     result = pinger.hello("nobody");
172     assertNull(result);
173     int cnt = pinger.getPingCount();
174     assertTrue("Count should be incremented", cnt > 0);
175     int newcnt = pinger.incrementCount(5);
176     assertEquals("Counter should have incremented by 5", cnt+5, newcnt);
177   }
178 
179   @Test
180   public void testSingleMethod() throws Throwable {
181     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
182 
183     List<? extends Row> rows = Lists.newArrayList(
184         new Get(ROW_A), new Get(ROW_B), new Get(ROW_C));
185 
186     Batch.Call<PingProtocol,String> call =  Batch.forMethod(PingProtocol.class,
187         "ping");
188     Map<byte[],String> results =
189         table.coprocessorExec(PingProtocol.class, ROW_A, ROW_C, call);
190 
191 
192     verifyRegionResults(table, results, ROW_A);
193     verifyRegionResults(table, results, ROW_B);
194     verifyRegionResults(table, results, ROW_C);
195 
196     Batch.Call<PingProtocol,String> helloCall =
197       Batch.forMethod(PingProtocol.class, "hello", "NAME");
198     results =
199         table.coprocessorExec(PingProtocol.class, ROW_A, ROW_C, helloCall);
200 
201 
202     verifyRegionResults(table, results, "Hello, NAME", ROW_A);
203     verifyRegionResults(table, results, "Hello, NAME", ROW_B);
204     verifyRegionResults(table, results, "Hello, NAME", ROW_C);
205   }
206 
207   @Test
208   public void testRowRange() throws Throwable {
209     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
210 
211     // test empty range
212     Map<byte[],String> results = table.coprocessorExec(PingProtocol.class,
213         null, null, new Batch.Call<PingProtocol,String>() {
214           public String call(PingProtocol instance) {
215             return instance.ping();
216           }
217         });
218     // should contain all three rows/regions
219     verifyRegionResults(table, results, ROW_A);
220     verifyRegionResults(table, results, ROW_B);
221     verifyRegionResults(table, results, ROW_C);
222 
223     // test start row + empty end
224     results = table.coprocessorExec(PingProtocol.class, ROW_BC, null,
225         new Batch.Call<PingProtocol,String>() {
226           public String call(PingProtocol instance) {
227             return instance.ping();
228           }
229         });
230     // should contain last 2 regions
231     HRegionLocation loc = table.getRegionLocation(ROW_A);
232     assertNull("Should be missing region for row aaa (prior to start row)",
233         results.get(loc.getRegionInfo().getRegionName()));
234     verifyRegionResults(table, results, ROW_B);
235     verifyRegionResults(table, results, ROW_C);
236 
237     // test empty start + end
238     results = table.coprocessorExec(PingProtocol.class, null, ROW_BC,
239         new Batch.Call<PingProtocol,String>() {
240           public String call(PingProtocol instance) {
241             return instance.ping();
242           }
243         });
244     // should contain the first 2 regions
245     verifyRegionResults(table, results, ROW_A);
246     verifyRegionResults(table, results, ROW_B);
247     loc = table.getRegionLocation(ROW_C);
248     assertNull("Should be missing region for row ccc (past stop row)",
249         results.get(loc.getRegionInfo().getRegionName()));
250 
251     // test explicit start + end
252     results = table.coprocessorExec(PingProtocol.class, ROW_AB, ROW_BC,
253         new Batch.Call<PingProtocol,String>() {
254           public String call(PingProtocol instance) {
255             return instance.ping();
256           }
257         });
258     // should contain first 2 regions
259     verifyRegionResults(table, results, ROW_A);
260     verifyRegionResults(table, results, ROW_B);
261     loc = table.getRegionLocation(ROW_C);
262     assertNull("Should be missing region for row ccc (past stop row)",
263         results.get(loc.getRegionInfo().getRegionName()));
264 
265     // test single region
266     results = table.coprocessorExec(PingProtocol.class, ROW_B, ROW_BC,
267         new Batch.Call<PingProtocol,String>() {
268           public String call(PingProtocol instance) {
269             return instance.ping();
270           }
271         });
272     // should only contain region bbb
273     verifyRegionResults(table, results, ROW_B);
274     loc = table.getRegionLocation(ROW_A);
275     assertNull("Should be missing region for row aaa (prior to start)",
276         results.get(loc.getRegionInfo().getRegionName()));
277     loc = table.getRegionLocation(ROW_C);
278     assertNull("Should be missing region for row ccc (past stop row)",
279         results.get(loc.getRegionInfo().getRegionName()));
280   }
281 
282   @Test
283   public void testCompountCall() throws Throwable {
284     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
285 
286     Map<byte[],String> results = table.coprocessorExec(PingProtocol.class,
287         ROW_A, ROW_C,
288         new Batch.Call<PingProtocol,String>() {
289           public String call(PingProtocol instance) {
290             return instance.hello(instance.ping());
291           }
292         });
293 
294     verifyRegionResults(table, results, "Hello, pong", ROW_A);
295     verifyRegionResults(table, results, "Hello, pong", ROW_B);
296     verifyRegionResults(table, results, "Hello, pong", ROW_C);
297   }
298 
299   @Test
300   public void testNullCall() throws Throwable {
301     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
302 
303     Map<byte[],String> results = table.coprocessorExec(PingProtocol.class,
304         ROW_A, ROW_C,
305         new Batch.Call<PingProtocol,String>() {
306           public String call(PingProtocol instance) {
307             return instance.hello(null);
308           }
309         });
310 
311     verifyRegionResults(table, results, "Who are you?", ROW_A);
312     verifyRegionResults(table, results, "Who are you?", ROW_B);
313     verifyRegionResults(table, results, "Who are you?", ROW_C);
314   }
315 
316   @Test
317   public void testNullReturn() throws Throwable {
318     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
319 
320     Map<byte[],String> results = table.coprocessorExec(PingProtocol.class,
321         ROW_A, ROW_C,
322         new Batch.Call<PingProtocol,String>(){
323           public String call(PingProtocol instance) {
324             return instance.hello("nobody");
325           }
326         });
327 
328     verifyRegionResults(table, results, null, ROW_A);
329     verifyRegionResults(table, results, null, ROW_B);
330     verifyRegionResults(table, results, null, ROW_C);
331   }
332 
333   @Test
334   public void testVoidReturnType() throws Throwable {
335     HTable table = new HTable(util.getConfiguration(), TEST_TABLE);
336 
337     Map<byte[],Object> results = table.coprocessorExec(PingProtocol.class,
338         ROW_A, ROW_C,
339         new Batch.Call<PingProtocol,Object>(){
340           public Object call(PingProtocol instance) {
341             instance.noop();
342             return null;
343           }
344         });
345 
346     assertEquals("Should have results from three regions", 3, results.size());
347     // all results should be null
348     for (Object v : results.values()) {
349       assertNull(v);
350     }
351   }
352 
353   private void verifyRegionResults(HTable table,
354       Map<byte[],String> results, byte[] row) throws Exception {
355     verifyRegionResults(table, results, "pong", row);
356   }
357 
358   private void verifyRegionResults(HTable table,
359       Map<byte[],String> results, String expected, byte[] row)
360   throws Exception {
361     HRegionLocation loc = table.getRegionLocation(row);
362     byte[] region = loc.getRegionInfo().getRegionName();
363     assertTrue("Results should contain region " +
364         Bytes.toStringBinary(region)+" for row '"+Bytes.toStringBinary(row)+"'",
365         results.containsKey(region));
366     assertEquals("Invalid result for row '"+Bytes.toStringBinary(row)+"'",
367         expected, results.get(region));
368   }
369 
370   @org.junit.Rule
371   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
372     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
373 }
374