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