1   /*
2    * Copyright 2009 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.thrift;
21  
22  import static org.junit.Assert.*;
23  
24  import java.io.IOException;
25  import java.nio.ByteBuffer;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.hadoop.hbase.HBaseTestingUtility;
30  import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
31  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
32  import org.apache.hadoop.hbase.thrift.generated.Mutation;
33  import org.apache.hadoop.hbase.thrift.generated.TCell;
34  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
35  import org.apache.hadoop.hbase.util.Bytes;
36  import org.junit.AfterClass;
37  import org.junit.BeforeClass;
38  import org.junit.Test;
39  
40  /**
41   * Unit testing for ThriftServer.HBaseHandler, a part of the
42   * org.apache.hadoop.hbase.thrift package.
43   */
44  public class TestThriftServer {
45    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
46    protected static final int MAXVERSIONS = 3;
47    private static ByteBuffer $bb(String i) {
48      return ByteBuffer.wrap(Bytes.toBytes(i));
49    }
50    // Static names for tables, columns, rows, and values
51    private static ByteBuffer tableAname = $bb("tableA");
52    private static ByteBuffer tableBname = $bb("tableB");
53    private static ByteBuffer columnAname = $bb("columnA:");
54    private static ByteBuffer columnBname = $bb("columnB:");
55    private static ByteBuffer rowAname = $bb("rowA");
56    private static ByteBuffer rowBname = $bb("rowB");
57    private static ByteBuffer valueAname = $bb("valueA");
58    private static ByteBuffer valueBname = $bb("valueB");
59    private static ByteBuffer valueCname = $bb("valueC");
60    private static ByteBuffer valueDname = $bb("valueD");
61  
62    @BeforeClass
63    public static void beforeClass() throws Exception {
64      UTIL.startMiniCluster();
65    }
66  
67    @AfterClass
68    public static void afterClass() throws Exception {
69      UTIL.shutdownMiniCluster();
70    }
71  
72    /**
73     * Runs all of the tests under a single JUnit test method.  We
74     * consolidate all testing to one method because HBaseClusterTestCase
75     * is prone to OutOfMemoryExceptions when there are three or more
76     * JUnit test methods.
77     *
78     * @throws Exception
79     */
80    public void testAll() throws Exception {
81      // Run all tests
82      doTestTableCreateDrop();
83      doTestTableMutations();
84      doTestTableTimestampsAndColumns();
85      doTestTableScanners();
86      doTestGetTableRegions();
87    }
88  
89    /**
90     * Tests for creating, enabling, disabling, and deleting tables.  Also
91     * tests that creating a table with an invalid column name yields an
92     * IllegalArgument exception.
93     *
94     * @throws Exception
95     */
96    @Test
97    public void doTestTableCreateDrop() throws Exception {
98      ThriftServer.HBaseHandler handler =
99        new ThriftServer.HBaseHandler(UTIL.getConfiguration());
100 
101     // Create/enable/disable/delete tables, ensure methods act correctly
102     assertEquals(handler.getTableNames().size(), 0);
103     handler.createTable(tableAname, getColumnDescriptors());
104     assertEquals(handler.getTableNames().size(), 1);
105     assertEquals(handler.getColumnDescriptors(tableAname).size(), 2);
106     assertTrue(handler.isTableEnabled(tableAname));
107     handler.createTable(tableBname, new ArrayList<ColumnDescriptor>());
108     assertEquals(handler.getTableNames().size(), 2);
109     handler.disableTable(tableBname);
110     assertFalse(handler.isTableEnabled(tableBname));
111     handler.deleteTable(tableBname);
112     assertEquals(handler.getTableNames().size(), 1);
113     handler.disableTable(tableAname);
114     /* TODO Reenable.
115     assertFalse(handler.isTableEnabled(tableAname));
116     handler.enableTable(tableAname);
117     assertTrue(handler.isTableEnabled(tableAname));
118     handler.disableTable(tableAname);*/
119     handler.deleteTable(tableAname);
120   }
121   
122   /**
123    * Tests adding a series of Mutations and BatchMutations, including a
124    * delete mutation.  Also tests data retrieval, and getting back multiple
125    * versions.
126    *
127    * @throws Exception
128    */
129   public void doTestTableMutations() throws Exception {
130     // Setup
131     ThriftServer.HBaseHandler handler =
132       new ThriftServer.HBaseHandler(UTIL.getConfiguration());
133     handler.createTable(tableAname, getColumnDescriptors());
134 
135     // Apply a few Mutations to rowA
136     //     mutations.add(new Mutation(false, columnAname, valueAname));
137     //     mutations.add(new Mutation(false, columnBname, valueBname));
138     handler.mutateRow(tableAname, rowAname, getMutations());
139 
140     // Assert that the changes were made
141     assertEquals(valueAname,
142       handler.get(tableAname, rowAname, columnAname).get(0).value);
143     TRowResult rowResult1 = handler.getRow(tableAname, rowAname).get(0);
144     assertEquals(rowAname, rowResult1.row);
145     assertEquals(valueBname,
146       rowResult1.columns.get(columnBname).value);
147 
148     // Apply a few BatchMutations for rowA and rowB
149     // rowAmutations.add(new Mutation(true, columnAname, null));
150     // rowAmutations.add(new Mutation(false, columnBname, valueCname));
151     // batchMutations.add(new BatchMutation(rowAname, rowAmutations));
152     // Mutations to rowB
153     // rowBmutations.add(new Mutation(false, columnAname, valueCname));
154     // rowBmutations.add(new Mutation(false, columnBname, valueDname));
155     // batchMutations.add(new BatchMutation(rowBname, rowBmutations));
156     handler.mutateRows(tableAname, getBatchMutations());
157 
158     // Assert that changes were made to rowA
159     List<TCell> cells = handler.get(tableAname, rowAname, columnAname);
160     assertFalse(cells.size() > 0);
161     assertEquals(valueCname, handler.get(tableAname, rowAname, columnBname).get(0).value);
162     List<TCell> versions = handler.getVer(tableAname, rowAname, columnBname, MAXVERSIONS);
163     assertEquals(valueCname, versions.get(0).value);
164     assertEquals(valueBname, versions.get(1).value);
165 
166     // Assert that changes were made to rowB
167     TRowResult rowResult2 = handler.getRow(tableAname, rowBname).get(0);
168     assertEquals(rowBname, rowResult2.row);
169     assertEquals(valueCname, rowResult2.columns.get(columnAname).value);
170 	  assertEquals(valueDname, rowResult2.columns.get(columnBname).value);
171 
172     // Apply some deletes
173     handler.deleteAll(tableAname, rowAname, columnBname);
174     handler.deleteAllRow(tableAname, rowBname);
175 
176     // Assert that the deletes were applied
177     int size = handler.get(tableAname, rowAname, columnBname).size();
178     assertEquals(0, size);
179     size = handler.getRow(tableAname, rowBname).size();
180     assertEquals(0, size);
181 
182     // Try null mutation
183     List<Mutation> mutations = new ArrayList<Mutation>();
184     mutations.add(new Mutation(false, columnAname, null));
185     handler.mutateRow(tableAname, rowAname, mutations);
186     TRowResult rowResult3 = handler.getRow(tableAname, rowAname).get(0);
187     assertEquals(rowAname, rowResult3.row);
188     assertEquals(0, rowResult3.columns.get(columnAname).value.array().length);
189 
190     // Teardown
191     handler.disableTable(tableAname);
192     handler.deleteTable(tableAname);
193   }
194 
195   /**
196    * Similar to testTableMutations(), except Mutations are applied with
197    * specific timestamps and data retrieval uses these timestamps to
198    * extract specific versions of data.
199    *
200    * @throws Exception
201    */
202   public void doTestTableTimestampsAndColumns() throws Exception {
203     // Setup
204     ThriftServer.HBaseHandler handler =
205       new ThriftServer.HBaseHandler(UTIL.getConfiguration());
206     handler.createTable(tableAname, getColumnDescriptors());
207 
208     // Apply timestamped Mutations to rowA
209     long time1 = System.currentTimeMillis();
210     handler.mutateRowTs(tableAname, rowAname, getMutations(), time1);
211 
212     Thread.sleep(1000);
213 
214     // Apply timestamped BatchMutations for rowA and rowB
215     long time2 = System.currentTimeMillis();
216     handler.mutateRowsTs(tableAname, getBatchMutations(), time2);
217 
218     // Apply an overlapping timestamped mutation to rowB
219     handler.mutateRowTs(tableAname, rowBname, getMutations(), time2);
220 
221     // the getVerTs is [inf, ts) so you need to increment one.
222     time1 += 1;
223     time2 += 2;
224 
225     // Assert that the timestamp-related methods retrieve the correct data
226     assertEquals(2, handler.getVerTs(tableAname, rowAname, columnBname, time2,
227       MAXVERSIONS).size());
228     assertEquals(1, handler.getVerTs(tableAname, rowAname, columnBname, time1,
229       MAXVERSIONS).size());
230 
231     TRowResult rowResult1 = handler.getRowTs(tableAname, rowAname, time1).get(0);
232     TRowResult rowResult2 = handler.getRowTs(tableAname, rowAname, time2).get(0);
233     // columnA was completely deleted
234     //assertTrue(Bytes.equals(rowResult1.columns.get(columnAname).value, valueAname));
235     assertEquals(rowResult1.columns.get(columnBname).value, valueBname);
236     assertEquals(rowResult2.columns.get(columnBname).value, valueCname);
237 
238     // ColumnAname has been deleted, and will never be visible even with a getRowTs()
239     assertFalse(rowResult2.columns.containsKey(columnAname));
240 
241     List<ByteBuffer> columns = new ArrayList<ByteBuffer>();
242     columns.add(columnBname);
243 
244     rowResult1 = handler.getRowWithColumns(tableAname, rowAname, columns).get(0);
245     assertEquals(rowResult1.columns.get(columnBname).value, valueCname);
246     assertFalse(rowResult1.columns.containsKey(columnAname));
247 
248     rowResult1 = handler.getRowWithColumnsTs(tableAname, rowAname, columns, time1).get(0);
249     assertEquals(rowResult1.columns.get(columnBname).value, valueBname);
250     assertFalse(rowResult1.columns.containsKey(columnAname));
251 
252     // Apply some timestamped deletes
253     // this actually deletes _everything_.
254     // nukes everything in columnB: forever.
255     handler.deleteAllTs(tableAname, rowAname, columnBname, time1);
256     handler.deleteAllRowTs(tableAname, rowBname, time2);
257 
258     // Assert that the timestamp-related methods retrieve the correct data
259     int size = handler.getVerTs(tableAname, rowAname, columnBname, time1, MAXVERSIONS).size();
260     assertEquals(0, size);
261 
262     size = handler.getVerTs(tableAname, rowAname, columnBname, time2, MAXVERSIONS).size();
263     assertEquals(1, size);
264 
265     // should be available....
266     assertEquals(handler.get(tableAname, rowAname, columnBname).get(0).value, valueCname);
267 
268     assertEquals(0, handler.getRow(tableAname, rowBname).size());
269 
270     // Teardown
271     handler.disableTable(tableAname);
272     handler.deleteTable(tableAname);
273   }
274 
275   /**
276    * Tests the four different scanner-opening methods (with and without
277    * a stoprow, with and without a timestamp).
278    *
279    * @throws Exception
280    */
281   public void doTestTableScanners() throws Exception {
282     // Setup
283     ThriftServer.HBaseHandler handler =
284       new ThriftServer.HBaseHandler(UTIL.getConfiguration());
285     handler.createTable(tableAname, getColumnDescriptors());
286 
287     // Apply timestamped Mutations to rowA
288     long time1 = System.currentTimeMillis();
289     handler.mutateRowTs(tableAname, rowAname, getMutations(), time1);
290 
291     // Sleep to assure that 'time1' and 'time2' will be different even with a
292     // coarse grained system timer.
293     Thread.sleep(1000);
294 
295     // Apply timestamped BatchMutations for rowA and rowB
296     long time2 = System.currentTimeMillis();
297     handler.mutateRowsTs(tableAname, getBatchMutations(), time2);
298 
299     time1 += 1;
300 
301     // Test a scanner on all rows and all columns, no timestamp
302     int scanner1 = handler.scannerOpen(tableAname, rowAname, getColumnList(true, true));
303     TRowResult rowResult1a = handler.scannerGet(scanner1).get(0);
304     assertEquals(rowResult1a.row, rowAname);
305     // This used to be '1'.  I don't know why when we are asking for two columns
306     // and when the mutations above would seem to add two columns to the row.
307     // -- St.Ack 05/12/2009
308     assertEquals(rowResult1a.columns.size(), 1);
309     assertEquals(rowResult1a.columns.get(columnBname).value, valueCname);
310 
311     TRowResult rowResult1b = handler.scannerGet(scanner1).get(0);
312     assertEquals(rowResult1b.row, rowBname);
313     assertEquals(rowResult1b.columns.size(), 2);
314     assertEquals(rowResult1b.columns.get(columnAname).value, valueCname);
315     assertEquals(rowResult1b.columns.get(columnBname).value, valueDname);
316     closeScanner(scanner1, handler);
317 
318     // Test a scanner on all rows and all columns, with timestamp
319     int scanner2 = handler.scannerOpenTs(tableAname, rowAname, getColumnList(true, true), time1);
320     TRowResult rowResult2a = handler.scannerGet(scanner2).get(0);
321     assertEquals(rowResult2a.columns.size(), 1);
322     // column A deleted, does not exist.
323     //assertTrue(Bytes.equals(rowResult2a.columns.get(columnAname).value, valueAname));
324     assertEquals(rowResult2a.columns.get(columnBname).value, valueBname);
325     closeScanner(scanner2, handler);
326 
327     // Test a scanner on the first row and first column only, no timestamp
328     int scanner3 = handler.scannerOpenWithStop(tableAname, rowAname, rowBname,
329         getColumnList(true, false));
330     closeScanner(scanner3, handler);
331 
332     // Test a scanner on the first row and second column only, with timestamp
333     int scanner4 = handler.scannerOpenWithStopTs(tableAname, rowAname, rowBname,
334         getColumnList(false, true), time1);
335     TRowResult rowResult4a = handler.scannerGet(scanner4).get(0);
336     assertEquals(rowResult4a.columns.size(), 1);
337     assertEquals(rowResult4a.columns.get(columnBname).value, valueBname);
338 
339     // Teardown
340     handler.disableTable(tableAname);
341     handler.deleteTable(tableAname);
342   }
343   
344   /**
345    * For HBASE-2556
346    * Tests for GetTableRegions
347    *
348    * @throws Exception
349    */
350   public void doTestGetTableRegions() throws Exception {
351     ThriftServer.HBaseHandler handler =
352       new ThriftServer.HBaseHandler(UTIL.getConfiguration());
353     handler.createTable(tableAname, getColumnDescriptors());
354     int regionCount = handler.getTableRegions(tableAname).size();
355     assertEquals("empty table should have only 1 region, " +
356             "but found " + regionCount, regionCount, 1);
357     handler.disableTable(tableAname);    
358     handler.deleteTable(tableAname);
359     regionCount = handler.getTableRegions(tableAname).size();
360     assertEquals("non-existing table should have 0 region, " +
361             "but found " + regionCount, regionCount, 0);    
362   } 
363   
364   /**
365    *
366    * @return a List of ColumnDescriptors for use in creating a table.  Has one
367    * default ColumnDescriptor and one ColumnDescriptor with fewer versions
368    */
369   private List<ColumnDescriptor> getColumnDescriptors() {
370     ArrayList<ColumnDescriptor> cDescriptors = new ArrayList<ColumnDescriptor>();
371 
372     // A default ColumnDescriptor
373     ColumnDescriptor cDescA = new ColumnDescriptor();
374     cDescA.name = columnAname;
375     cDescriptors.add(cDescA);
376 
377     // A slightly customized ColumnDescriptor (only 2 versions)
378     ColumnDescriptor cDescB = new ColumnDescriptor(columnBname, 2, "NONE",
379         false, "NONE", 0, 0, false, -1);
380     cDescriptors.add(cDescB);
381 
382     return cDescriptors;
383   }
384 
385   /**
386    *
387    * @param includeA whether or not to include columnA
388    * @param includeB whether or not to include columnB
389    * @return a List of column names for use in retrieving a scanner
390    */
391   private List<ByteBuffer> getColumnList(boolean includeA, boolean includeB) {
392     List<ByteBuffer> columnList = new ArrayList<ByteBuffer>();
393     if (includeA) columnList.add(columnAname);
394     if (includeB) columnList.add(columnBname);
395     return columnList;
396   }
397 
398   /**
399    *
400    * @return a List of Mutations for a row, with columnA having valueA
401    * and columnB having valueB
402    */
403   private List<Mutation> getMutations() {
404     List<Mutation> mutations = new ArrayList<Mutation>();
405     mutations.add(new Mutation(false, columnAname, valueAname));
406     mutations.add(new Mutation(false, columnBname, valueBname));
407     return mutations;
408   }
409 
410   /**
411    *
412    * @return a List of BatchMutations with the following effects:
413    * (rowA, columnA): delete
414    * (rowA, columnB): place valueC
415    * (rowB, columnA): place valueC
416    * (rowB, columnB): place valueD
417    */
418   private List<BatchMutation> getBatchMutations() {
419     List<BatchMutation> batchMutations = new ArrayList<BatchMutation>();
420 
421     // Mutations to rowA.  You can't mix delete and put anymore.
422     List<Mutation> rowAmutations = new ArrayList<Mutation>();
423     rowAmutations.add(new Mutation(true, columnAname, null));
424     batchMutations.add(new BatchMutation(rowAname, rowAmutations));
425 
426     rowAmutations = new ArrayList<Mutation>();
427     rowAmutations.add(new Mutation(false, columnBname, valueCname));
428     batchMutations.add(new BatchMutation(rowAname, rowAmutations));
429 
430     // Mutations to rowB
431     List<Mutation> rowBmutations = new ArrayList<Mutation>();
432     rowBmutations.add(new Mutation(false, columnAname, valueCname));
433     rowBmutations.add(new Mutation(false, columnBname, valueDname));
434     batchMutations.add(new BatchMutation(rowBname, rowBmutations));
435 
436     return batchMutations;
437   }
438 
439   /**
440    * Asserts that the passed scanner is exhausted, and then closes
441    * the scanner.
442    *
443    * @param scannerId the scanner to close
444    * @param handler the HBaseHandler interfacing to HBase
445    * @throws Exception
446    */
447   private void closeScanner(int scannerId, ThriftServer.HBaseHandler handler) throws Exception {
448     handler.scannerGet(scannerId);
449     handler.scannerClose(scannerId);
450   }
451 }