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