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