| /* |
| * Copyright 2009 The Apache Software Foundation |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hbase.thrift; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.hadoop.hbase.HBaseClusterTestCase; |
| import org.apache.hadoop.hbase.thrift.generated.BatchMutation; |
| import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor; |
| import org.apache.hadoop.hbase.thrift.generated.Mutation; |
| import org.apache.hadoop.hbase.thrift.generated.TCell; |
| import org.apache.hadoop.hbase.thrift.generated.TRowResult; |
| import org.apache.hadoop.hbase.util.Bytes; |
| |
| /** |
| * Unit testing for ThriftServer.HBaseHandler, a part of the |
| * org.apache.hadoop.hbase.thrift package. |
| */ |
| public class TestThriftServer extends HBaseClusterTestCase { |
| |
| // Static names for tables, columns, rows, and values |
| private static byte[] tableAname = Bytes.toBytes("tableA"); |
| private static byte[] tableBname = Bytes.toBytes("tableB"); |
| private static byte[] columnAname = Bytes.toBytes("columnA:"); |
| private static byte[] columnBname = Bytes.toBytes("columnB:"); |
| private static byte[] rowAname = Bytes.toBytes("rowA"); |
| private static byte[] rowBname = Bytes.toBytes("rowB"); |
| private static byte[] valueAname = Bytes.toBytes("valueA"); |
| private static byte[] valueBname = Bytes.toBytes("valueB"); |
| private static byte[] valueCname = Bytes.toBytes("valueC"); |
| private static byte[] valueDname = Bytes.toBytes("valueD"); |
| |
| /** |
| * Runs all of the tests under a single JUnit test method. We |
| * consolidate all testing to one method because HBaseClusterTestCase |
| * is prone to OutOfMemoryExceptions when there are three or more |
| * JUnit test methods. |
| * |
| * @throws Exception |
| */ |
| public void testAll() throws Exception { |
| // Run all tests |
| doTestTableCreateDrop(); |
| doTestTableMutations(); |
| doTestTableTimestampsAndColumns(); |
| doTestTableScanners(); |
| } |
| |
| /** |
| * Tests for creating, enabling, disabling, and deleting tables. Also |
| * tests that creating a table with an invalid column name yields an |
| * IllegalArgument exception. |
| * |
| * @throws Exception |
| */ |
| public void doTestTableCreateDrop() throws Exception { |
| ThriftServer.HBaseHandler handler = new ThriftServer.HBaseHandler(this.conf); |
| |
| // Create/enable/disable/delete tables, ensure methods act correctly |
| assertEquals(handler.getTableNames().size(), 0); |
| handler.createTable(tableAname, getColumnDescriptors()); |
| assertEquals(handler.getTableNames().size(), 1); |
| assertEquals(handler.getColumnDescriptors(tableAname).size(), 2); |
| assertTrue(handler.isTableEnabled(tableAname)); |
| handler.createTable(tableBname, new ArrayList<ColumnDescriptor>()); |
| assertEquals(handler.getTableNames().size(), 2); |
| handler.disableTable(tableBname); |
| assertFalse(handler.isTableEnabled(tableBname)); |
| handler.deleteTable(tableBname); |
| assertEquals(handler.getTableNames().size(), 1); |
| handler.disableTable(tableAname); |
| /* TODO Reenable. |
| assertFalse(handler.isTableEnabled(tableAname)); |
| handler.enableTable(tableAname); |
| assertTrue(handler.isTableEnabled(tableAname)); |
| handler.disableTable(tableAname);*/ |
| handler.deleteTable(tableAname); |
| } |
| |
| /** |
| * Tests adding a series of Mutations and BatchMutations, including a |
| * delete mutation. Also tests data retrieval, and getting back multiple |
| * versions. |
| * |
| * @throws Exception |
| */ |
| public void doTestTableMutations() throws Exception { |
| // Setup |
| ThriftServer.HBaseHandler handler = new ThriftServer.HBaseHandler(this.conf); |
| handler.createTable(tableAname, getColumnDescriptors()); |
| |
| // Apply a few Mutations to rowA |
| // mutations.add(new Mutation(false, columnAname, valueAname)); |
| // mutations.add(new Mutation(false, columnBname, valueBname)); |
| handler.mutateRow(tableAname, rowAname, getMutations()); |
| |
| // Assert that the changes were made |
| assertTrue(Bytes.equals(valueAname, |
| handler.get(tableAname, rowAname, columnAname).get(0).value)); |
| TRowResult rowResult1 = handler.getRow(tableAname, rowAname).get(0); |
| assertTrue(Bytes.equals(rowAname, rowResult1.row)); |
| assertTrue(Bytes.equals(valueBname, |
| rowResult1.columns.get(columnBname).value)); |
| |
| // Apply a few BatchMutations for rowA and rowB |
| // rowAmutations.add(new Mutation(true, columnAname, null)); |
| // rowAmutations.add(new Mutation(false, columnBname, valueCname)); |
| // batchMutations.add(new BatchMutation(rowAname, rowAmutations)); |
| // Mutations to rowB |
| // rowBmutations.add(new Mutation(false, columnAname, valueCname)); |
| // rowBmutations.add(new Mutation(false, columnBname, valueDname)); |
| // batchMutations.add(new BatchMutation(rowBname, rowBmutations)); |
| handler.mutateRows(tableAname, getBatchMutations()); |
| |
| // Assert that changes were made to rowA |
| List<TCell> cells = handler.get(tableAname, rowAname, columnAname); |
| assertFalse(cells.size() > 0); |
| assertTrue(Bytes.equals(valueCname, handler.get(tableAname, rowAname, columnBname).get(0).value)); |
| List<TCell> versions = handler.getVer(tableAname, rowAname, columnBname, MAXVERSIONS); |
| assertTrue(Bytes.equals(valueCname, versions.get(0).value)); |
| assertTrue(Bytes.equals(valueBname, versions.get(1).value)); |
| |
| // Assert that changes were made to rowB |
| TRowResult rowResult2 = handler.getRow(tableAname, rowBname).get(0); |
| assertTrue(Bytes.equals(rowBname, rowResult2.row)); |
| assertTrue(Bytes.equals(valueCname, rowResult2.columns.get(columnAname).value)); |
| assertTrue(Bytes.equals(valueDname, rowResult2.columns.get(columnBname).value)); |
| |
| // Apply some deletes |
| handler.deleteAll(tableAname, rowAname, columnBname); |
| handler.deleteAllRow(tableAname, rowBname); |
| |
| // Assert that the deletes were applied |
| int size = handler.get(tableAname, rowAname, columnBname).size(); |
| assertEquals(0, size); |
| size = handler.getRow(tableAname, rowBname).size(); |
| assertEquals(0, size); |
| |
| // Teardown |
| handler.disableTable(tableAname); |
| handler.deleteTable(tableAname); |
| } |
| |
| /** |
| * Similar to testTableMutations(), except Mutations are applied with |
| * specific timestamps and data retrieval uses these timestamps to |
| * extract specific versions of data. |
| * |
| * @throws Exception |
| */ |
| public void doTestTableTimestampsAndColumns() throws Exception { |
| // Setup |
| ThriftServer.HBaseHandler handler = new ThriftServer.HBaseHandler(this.conf); |
| handler.createTable(tableAname, getColumnDescriptors()); |
| |
| // Apply timestamped Mutations to rowA |
| long time1 = System.currentTimeMillis(); |
| handler.mutateRowTs(tableAname, rowAname, getMutations(), time1); |
| |
| Thread.sleep(1000); |
| |
| // Apply timestamped BatchMutations for rowA and rowB |
| long time2 = System.currentTimeMillis(); |
| handler.mutateRowsTs(tableAname, getBatchMutations(), time2); |
| |
| // Apply an overlapping timestamped mutation to rowB |
| handler.mutateRowTs(tableAname, rowBname, getMutations(), time2); |
| |
| // the getVerTs is [inf, ts) so you need to increment one. |
| time1 += 1; |
| time2 += 2; |
| |
| // Assert that the timestamp-related methods retrieve the correct data |
| assertEquals(2, handler.getVerTs(tableAname, rowAname, columnBname, time2, |
| MAXVERSIONS).size()); |
| assertEquals(1, handler.getVerTs(tableAname, rowAname, columnBname, time1, |
| MAXVERSIONS).size()); |
| |
| TRowResult rowResult1 = handler.getRowTs(tableAname, rowAname, time1).get(0); |
| TRowResult rowResult2 = handler.getRowTs(tableAname, rowAname, time2).get(0); |
| // columnA was completely deleted |
| //assertTrue(Bytes.equals(rowResult1.columns.get(columnAname).value, valueAname)); |
| assertTrue(Bytes.equals(rowResult1.columns.get(columnBname).value, valueBname)); |
| assertTrue(Bytes.equals(rowResult2.columns.get(columnBname).value, valueCname)); |
| |
| // ColumnAname has been deleted, and will never be visible even with a getRowTs() |
| assertFalse(rowResult2.columns.containsKey(columnAname)); |
| |
| List<byte[]> columns = new ArrayList<byte[]>(); |
| columns.add(columnBname); |
| |
| rowResult1 = handler.getRowWithColumns(tableAname, rowAname, columns).get(0); |
| assertTrue(Bytes.equals(rowResult1.columns.get(columnBname).value, valueCname)); |
| assertFalse(rowResult1.columns.containsKey(columnAname)); |
| |
| rowResult1 = handler.getRowWithColumnsTs(tableAname, rowAname, columns, time1).get(0); |
| assertTrue(Bytes.equals(rowResult1.columns.get(columnBname).value, valueBname)); |
| assertFalse(rowResult1.columns.containsKey(columnAname)); |
| |
| // Apply some timestamped deletes |
| // this actually deletes _everything_. |
| // nukes everything in columnB: forever. |
| handler.deleteAllTs(tableAname, rowAname, columnBname, time1); |
| handler.deleteAllRowTs(tableAname, rowBname, time2); |
| |
| // Assert that the timestamp-related methods retrieve the correct data |
| int size = handler.getVerTs(tableAname, rowAname, columnBname, time1, MAXVERSIONS).size(); |
| assertEquals(0, size); |
| |
| size = handler.getVerTs(tableAname, rowAname, columnBname, time2, MAXVERSIONS).size(); |
| assertEquals(1, size); |
| |
| // should be available.... |
| assertTrue(Bytes.equals(handler.get(tableAname, rowAname, columnBname).get(0).value, valueCname)); |
| |
| assertEquals(0, handler.getRow(tableAname, rowBname).size()); |
| |
| // Teardown |
| handler.disableTable(tableAname); |
| handler.deleteTable(tableAname); |
| } |
| |
| /** |
| * Tests the four different scanner-opening methods (with and without |
| * a stoprow, with and without a timestamp). |
| * |
| * @throws Exception |
| */ |
| public void doTestTableScanners() throws Exception { |
| // Setup |
| ThriftServer.HBaseHandler handler = new ThriftServer.HBaseHandler(this.conf); |
| handler.createTable(tableAname, getColumnDescriptors()); |
| |
| // Apply timestamped Mutations to rowA |
| long time1 = System.currentTimeMillis(); |
| handler.mutateRowTs(tableAname, rowAname, getMutations(), time1); |
| |
| // Sleep to assure that 'time1' and 'time2' will be different even with a |
| // coarse grained system timer. |
| Thread.sleep(1000); |
| |
| // Apply timestamped BatchMutations for rowA and rowB |
| long time2 = System.currentTimeMillis(); |
| handler.mutateRowsTs(tableAname, getBatchMutations(), time2); |
| |
| time1 += 1; |
| |
| // Test a scanner on all rows and all columns, no timestamp |
| int scanner1 = handler.scannerOpen(tableAname, rowAname, getColumnList(true, true)); |
| TRowResult rowResult1a = handler.scannerGet(scanner1).get(0); |
| assertTrue(Bytes.equals(rowResult1a.row, rowAname)); |
| // This used to be '1'. I don't know why when we are asking for two columns |
| // and when the mutations above would seem to add two columns to the row. |
| // -- St.Ack 05/12/2009 |
| assertEquals(rowResult1a.columns.size(), 1); |
| assertTrue(Bytes.equals(rowResult1a.columns.get(columnBname).value, valueCname)); |
| |
| TRowResult rowResult1b = handler.scannerGet(scanner1).get(0); |
| assertTrue(Bytes.equals(rowResult1b.row, rowBname)); |
| assertEquals(rowResult1b.columns.size(), 2); |
| assertTrue(Bytes.equals(rowResult1b.columns.get(columnAname).value, valueCname)); |
| assertTrue(Bytes.equals(rowResult1b.columns.get(columnBname).value, valueDname)); |
| closeScanner(scanner1, handler); |
| |
| // Test a scanner on all rows and all columns, with timestamp |
| int scanner2 = handler.scannerOpenTs(tableAname, rowAname, getColumnList(true, true), time1); |
| TRowResult rowResult2a = handler.scannerGet(scanner2).get(0); |
| assertEquals(rowResult2a.columns.size(), 1); |
| // column A deleted, does not exist. |
| //assertTrue(Bytes.equals(rowResult2a.columns.get(columnAname).value, valueAname)); |
| assertTrue(Bytes.equals(rowResult2a.columns.get(columnBname).value, valueBname)); |
| closeScanner(scanner2, handler); |
| |
| // Test a scanner on the first row and first column only, no timestamp |
| int scanner3 = handler.scannerOpenWithStop(tableAname, rowAname, rowBname, |
| getColumnList(true, false)); |
| closeScanner(scanner3, handler); |
| |
| // Test a scanner on the first row and second column only, with timestamp |
| int scanner4 = handler.scannerOpenWithStopTs(tableAname, rowAname, rowBname, |
| getColumnList(false, true), time1); |
| TRowResult rowResult4a = handler.scannerGet(scanner4).get(0); |
| assertEquals(rowResult4a.columns.size(), 1); |
| assertTrue(Bytes.equals(rowResult4a.columns.get(columnBname).value, valueBname)); |
| |
| // Teardown |
| handler.disableTable(tableAname); |
| handler.deleteTable(tableAname); |
| } |
| |
| /** |
| * |
| * @return a List of ColumnDescriptors for use in creating a table. Has one |
| * default ColumnDescriptor and one ColumnDescriptor with fewer versions |
| */ |
| private List<ColumnDescriptor> getColumnDescriptors() { |
| ArrayList<ColumnDescriptor> cDescriptors = new ArrayList<ColumnDescriptor>(); |
| |
| // A default ColumnDescriptor |
| ColumnDescriptor cDescA = new ColumnDescriptor(); |
| cDescA.name = columnAname; |
| cDescriptors.add(cDescA); |
| |
| // A slightly customized ColumnDescriptor (only 2 versions) |
| ColumnDescriptor cDescB = new ColumnDescriptor(columnBname, 2, "NONE", |
| false, "NONE", 0, 0, false, -1); |
| cDescriptors.add(cDescB); |
| |
| return cDescriptors; |
| } |
| |
| /** |
| * |
| * @param includeA whether or not to include columnA |
| * @param includeB whether or not to include columnB |
| * @return a List of column names for use in retrieving a scanner |
| */ |
| private List<byte[]> getColumnList(boolean includeA, boolean includeB) { |
| List<byte[]> columnList = new ArrayList<byte[]>(); |
| if (includeA) columnList.add(columnAname); |
| if (includeB) columnList.add(columnBname); |
| return columnList; |
| } |
| |
| /** |
| * |
| * @return a List of Mutations for a row, with columnA having valueA |
| * and columnB having valueB |
| */ |
| private List<Mutation> getMutations() { |
| List<Mutation> mutations = new ArrayList<Mutation>(); |
| mutations.add(new Mutation(false, columnAname, valueAname)); |
| mutations.add(new Mutation(false, columnBname, valueBname)); |
| return mutations; |
| } |
| |
| /** |
| * |
| * @return a List of BatchMutations with the following effects: |
| * (rowA, columnA): delete |
| * (rowA, columnB): place valueC |
| * (rowB, columnA): place valueC |
| * (rowB, columnB): place valueD |
| */ |
| private List<BatchMutation> getBatchMutations() { |
| List<BatchMutation> batchMutations = new ArrayList<BatchMutation>(); |
| |
| // Mutations to rowA. You can't mix delete and put anymore. |
| List<Mutation> rowAmutations = new ArrayList<Mutation>(); |
| rowAmutations.add(new Mutation(true, columnAname, null)); |
| batchMutations.add(new BatchMutation(rowAname, rowAmutations)); |
| |
| rowAmutations = new ArrayList<Mutation>(); |
| rowAmutations.add(new Mutation(false, columnBname, valueCname)); |
| batchMutations.add(new BatchMutation(rowAname, rowAmutations)); |
| |
| // Mutations to rowB |
| List<Mutation> rowBmutations = new ArrayList<Mutation>(); |
| rowBmutations.add(new Mutation(false, columnAname, valueCname)); |
| rowBmutations.add(new Mutation(false, columnBname, valueDname)); |
| batchMutations.add(new BatchMutation(rowBname, rowBmutations)); |
| |
| return batchMutations; |
| } |
| |
| /** |
| * Asserts that the passed scanner is exhausted, and then closes |
| * the scanner. |
| * |
| * @param scannerId the scanner to close |
| * @param handler the HBaseHandler interfacing to HBase |
| * @throws Exception |
| */ |
| private void closeScanner(int scannerId, ThriftServer.HBaseHandler handler) throws Exception { |
| handler.scannerGet(scannerId); |
| handler.scannerClose(scannerId); |
| } |
| } |