001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.thrift;
019
020import static org.apache.hadoop.hbase.thrift.Constants.COALESCE_INC_KEY;
021import static org.junit.Assert.assertArrayEquals;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertFalse;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.net.InetAddress;
029import java.nio.ByteBuffer;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035import java.util.concurrent.atomic.AtomicInteger;
036import java.util.stream.Collectors;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.hbase.CompatibilityFactory;
039import org.apache.hadoop.hbase.HBaseClassTestRule;
040import org.apache.hadoop.hbase.HBaseTestingUtility;
041import org.apache.hadoop.hbase.HColumnDescriptor;
042import org.apache.hadoop.hbase.HConstants;
043import org.apache.hadoop.hbase.HRegionInfo;
044import org.apache.hadoop.hbase.HTableDescriptor;
045import org.apache.hadoop.hbase.TableName;
046import org.apache.hadoop.hbase.client.Put;
047import org.apache.hadoop.hbase.client.Table;
048import org.apache.hadoop.hbase.filter.ParseFilter;
049import org.apache.hadoop.hbase.security.UserProvider;
050import org.apache.hadoop.hbase.test.MetricsAssertHelper;
051import org.apache.hadoop.hbase.testclassification.ClientTests;
052import org.apache.hadoop.hbase.testclassification.LargeTests;
053import org.apache.hadoop.hbase.thrift.ThriftMetrics.ThriftServerType;
054import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
055import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
056import org.apache.hadoop.hbase.thrift.generated.Hbase;
057import org.apache.hadoop.hbase.thrift.generated.IOError;
058import org.apache.hadoop.hbase.thrift.generated.Mutation;
059import org.apache.hadoop.hbase.thrift.generated.TAppend;
060import org.apache.hadoop.hbase.thrift.generated.TCell;
061import org.apache.hadoop.hbase.thrift.generated.TIncrement;
062import org.apache.hadoop.hbase.thrift.generated.TRegionInfo;
063import org.apache.hadoop.hbase.thrift.generated.TRowResult;
064import org.apache.hadoop.hbase.thrift.generated.TScan;
065import org.apache.hadoop.hbase.thrift.generated.TThriftServerType;
066import org.apache.hadoop.hbase.util.Bytes;
067import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
068import org.apache.hadoop.hbase.util.TableDescriptorChecker;
069import org.apache.hadoop.hbase.util.Threads;
070import org.apache.thrift.protocol.TBinaryProtocol;
071import org.apache.thrift.protocol.TProtocol;
072import org.apache.thrift.transport.TSocket;
073import org.apache.thrift.transport.TTransport;
074import org.junit.AfterClass;
075import org.junit.BeforeClass;
076import org.junit.ClassRule;
077import org.junit.Rule;
078import org.junit.Test;
079import org.junit.experimental.categories.Category;
080import org.junit.rules.TestName;
081import org.slf4j.Logger;
082import org.slf4j.LoggerFactory;
083
084/**
085 * Unit testing for ThriftServerRunner.HBaseServiceHandler, a part of the
086 * org.apache.hadoop.hbase.thrift package.
087 */
088@Category({ ClientTests.class, LargeTests.class })
089public class TestThriftServer {
090
091  @ClassRule
092  public static final HBaseClassTestRule CLASS_RULE =
093    HBaseClassTestRule.forClass(TestThriftServer.class);
094
095  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
096  private static final Logger LOG = LoggerFactory.getLogger(TestThriftServer.class);
097  private static final MetricsAssertHelper metricsHelper =
098    CompatibilityFactory.getInstance(MetricsAssertHelper.class);
099  protected static final int MAXVERSIONS = 3;
100
101  private static ByteBuffer asByteBuffer(String i) {
102    return ByteBuffer.wrap(Bytes.toBytes(i));
103  }
104
105  private static ByteBuffer asByteBuffer(long l) {
106    return ByteBuffer.wrap(Bytes.toBytes(l));
107  }
108
109  // Static names for tables, columns, rows, and values
110  private static ByteBuffer tableAname = asByteBuffer("tableA");
111  private static ByteBuffer tableBname = asByteBuffer("tableB");
112  private static ByteBuffer columnAname = asByteBuffer("columnA:");
113  private static ByteBuffer columnAAname = asByteBuffer("columnA:A");
114  private static ByteBuffer columnBname = asByteBuffer("columnB:");
115  private static ByteBuffer rowAname = asByteBuffer("rowA");
116  private static ByteBuffer rowBname = asByteBuffer("rowB");
117  private static ByteBuffer valueAname = asByteBuffer("valueA");
118  private static ByteBuffer valueBname = asByteBuffer("valueB");
119  private static ByteBuffer valueCname = asByteBuffer("valueC");
120  private static ByteBuffer valueDname = asByteBuffer("valueD");
121  private static ByteBuffer valueEname = asByteBuffer(100l);
122
123  @Rule
124  public TestName name = new TestName();
125
126  @BeforeClass
127  public static void beforeClass() throws Exception {
128    UTIL.getConfiguration().setBoolean(COALESCE_INC_KEY, true);
129    UTIL.getConfiguration().setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, false);
130    UTIL.getConfiguration().setInt("hbase.client.retries.number", 3);
131    UTIL.startMiniCluster();
132  }
133
134  @AfterClass
135  public static void afterClass() throws Exception {
136    UTIL.shutdownMiniCluster();
137  }
138
139  /**
140   * Runs all of the tests under a single JUnit test method. We consolidate all testing to one
141   * method because HBaseClusterTestCase is prone to OutOfMemoryExceptions when there are three or
142   * more JUnit test methods. n
143   */
144  @Test
145  public void testAll() throws Exception {
146    // Run all tests
147    doTestTableCreateDrop();
148    doTestThriftMetrics();
149    doTestTableMutations();
150    doTestTableTimestampsAndColumns();
151    doTestTableScanners();
152    doTestGetTableRegions();
153    doTestFilterRegistration();
154    doTestGetRegionInfo();
155    doTestIncrements();
156    doTestAppend();
157    doTestCheckAndPut();
158  }
159
160  /**
161   * Tests for creating, enabling, disabling, and deleting tables. Also tests that creating a table
162   * with an invalid column name yields an IllegalArgument exception. n
163   */
164  public void doTestTableCreateDrop() throws Exception {
165    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
166      UserProvider.instantiate(UTIL.getConfiguration()));
167    doTestTableCreateDrop(handler);
168  }
169
170  public static void doTestTableCreateDrop(Hbase.Iface handler) throws Exception {
171    createTestTables(handler);
172    dropTestTables(handler);
173  }
174
175  public static final class MySlowHBaseHandler extends ThriftHBaseServiceHandler
176    implements Hbase.Iface {
177
178    protected MySlowHBaseHandler(Configuration c) throws IOException {
179      super(c, UserProvider.instantiate(c));
180    }
181
182    @Override
183    public List<ByteBuffer> getTableNames() throws IOError {
184      Threads.sleepWithoutInterrupt(3000);
185      return super.getTableNames();
186    }
187  }
188
189  /**
190   * TODO: These counts are supposed to be zero but sometimes they are not, they are equal to the
191   * passed in maybe. Investigate why. My guess is they are set by the test that runs just previous
192   * to this one. Sometimes they are cleared. Sometimes not. nnnn
193   */
194  private int getCurrentCount(final String name, final int maybe, final ThriftMetrics metrics) {
195    int currentCount = 0;
196    try {
197      metricsHelper.assertCounter(name, maybe, metrics.getSource());
198      LOG.info("Shouldn't this be null? name=" + name + ", equals=" + maybe);
199      currentCount = maybe;
200    } catch (AssertionError e) {
201      // Ignore
202    }
203    return currentCount;
204  }
205
206  /**
207   * Tests if the metrics for thrift handler work correctly
208   */
209  public void doTestThriftMetrics() throws Exception {
210    LOG.info("START doTestThriftMetrics");
211    Configuration conf = UTIL.getConfiguration();
212    ThriftMetrics metrics = getMetrics(conf);
213    Hbase.Iface handler = getHandlerForMetricsTest(metrics, conf);
214    int currentCountCreateTable = getCurrentCount("createTable_num_ops", 2, metrics);
215    int currentCountDeleteTable = getCurrentCount("deleteTable_num_ops", 2, metrics);
216    int currentCountDisableTable = getCurrentCount("disableTable_num_ops", 2, metrics);
217    createTestTables(handler);
218    dropTestTables(handler);
219    ;
220    metricsHelper.assertCounter("createTable_num_ops", currentCountCreateTable + 2,
221      metrics.getSource());
222    metricsHelper.assertCounter("deleteTable_num_ops", currentCountDeleteTable + 2,
223      metrics.getSource());
224    metricsHelper.assertCounter("disableTable_num_ops", currentCountDisableTable + 2,
225      metrics.getSource());
226    handler.getTableNames(); // This will have an artificial delay.
227
228    // 3 to 6 seconds (to account for potential slowness), measured in nanoseconds
229    try {
230      metricsHelper.assertGaugeGt("getTableNames_avg_time", 3L * 1000 * 1000 * 1000,
231        metrics.getSource());
232      metricsHelper.assertGaugeLt("getTableNames_avg_time", 6L * 1000 * 1000 * 1000,
233        metrics.getSource());
234    } catch (AssertionError e) {
235      LOG.info("Fix me!  Why does this happen?  A concurrent cluster running?", e);
236    }
237  }
238
239  private static Hbase.Iface getHandlerForMetricsTest(ThriftMetrics metrics, Configuration conf)
240    throws Exception {
241    Hbase.Iface handler = new MySlowHBaseHandler(conf);
242    return HbaseHandlerMetricsProxy.newInstance((ThriftHBaseServiceHandler) handler, metrics, conf);
243  }
244
245  private static ThriftMetrics getMetrics(Configuration conf) throws Exception {
246    return new ThriftMetrics(conf, ThriftMetrics.ThriftServerType.ONE);
247  }
248
249  public static void createTestTables(Hbase.Iface handler) throws Exception {
250    // Create/enable/disable/delete tables, ensure methods act correctly
251    List<java.nio.ByteBuffer> bbs = handler.getTableNames();
252    assertEquals(bbs.stream().map(b -> Bytes.toString(b.array())).collect(Collectors.joining(",")),
253      0, bbs.size());
254    handler.createTable(tableAname, getColumnDescriptors());
255    assertEquals(1, handler.getTableNames().size());
256    assertEquals(2, handler.getColumnDescriptors(tableAname).size());
257    assertTrue(handler.isTableEnabled(tableAname));
258    handler.createTable(tableBname, getColumnDescriptors());
259    assertEquals(2, handler.getTableNames().size());
260  }
261
262  public static void checkTableList(Hbase.Iface handler) throws Exception {
263    assertTrue(handler.getTableNames().contains(tableAname));
264  }
265
266  public static void dropTestTables(Hbase.Iface handler) throws Exception {
267    handler.disableTable(tableBname);
268    assertFalse(handler.isTableEnabled(tableBname));
269    handler.deleteTable(tableBname);
270    assertEquals(1, handler.getTableNames().size());
271    handler.disableTable(tableAname);
272    assertFalse(handler.isTableEnabled(tableAname));
273    /*
274     * TODO Reenable. assertFalse(handler.isTableEnabled(tableAname));
275     * handler.enableTable(tableAname); assertTrue(handler.isTableEnabled(tableAname));
276     * handler.disableTable(tableAname);
277     */
278    handler.deleteTable(tableAname);
279    assertEquals(0, handler.getTableNames().size());
280  }
281
282  public void doTestIncrements() throws Exception {
283    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
284      UserProvider.instantiate(UTIL.getConfiguration()));
285    createTestTables(handler);
286    doTestIncrements(handler);
287    dropTestTables(handler);
288  }
289
290  public static void doTestIncrements(ThriftHBaseServiceHandler handler) throws Exception {
291    List<Mutation> mutations = new ArrayList<>(1);
292    mutations.add(new Mutation(false, columnAAname, valueEname, true));
293    mutations.add(new Mutation(false, columnAname, valueEname, true));
294    handler.mutateRow(tableAname, rowAname, mutations, null);
295    handler.mutateRow(tableAname, rowBname, mutations, null);
296
297    List<TIncrement> increments = new ArrayList<>(3);
298    increments.add(new TIncrement(tableAname, rowBname, columnAAname, 7));
299    increments.add(new TIncrement(tableAname, rowBname, columnAAname, 7));
300    increments.add(new TIncrement(tableAname, rowBname, columnAAname, 7));
301
302    int numIncrements = 60000;
303    for (int i = 0; i < numIncrements; i++) {
304      handler.increment(new TIncrement(tableAname, rowAname, columnAname, 2));
305      handler.incrementRows(increments);
306    }
307
308    Thread.sleep(1000);
309    long lv = handler.get(tableAname, rowAname, columnAname, null).get(0).value.getLong();
310    // Wait on all increments being flushed
311    while (handler.coalescer.getQueueSize() != 0)
312      Threads.sleep(10);
313    assertEquals((100 + (2 * numIncrements)), lv);
314
315    lv = handler.get(tableAname, rowBname, columnAAname, null).get(0).value.getLong();
316    assertEquals((100 + (3 * 7 * numIncrements)), lv);
317
318    assertTrue(handler.coalescer.getSuccessfulCoalescings() > 0);
319
320  }
321
322  /**
323   * Tests adding a series of Mutations and BatchMutations, including a delete mutation. Also tests
324   * data retrieval, and getting back multiple versions. n
325   */
326  public void doTestTableMutations() throws Exception {
327    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
328      UserProvider.instantiate(UTIL.getConfiguration()));
329    doTestTableMutations(handler);
330  }
331
332  public static void doTestTableMutations(Hbase.Iface handler) throws Exception {
333    // Setup
334    handler.createTable(tableAname, getColumnDescriptors());
335
336    // Apply a few Mutations to rowA
337    // mutations.add(new Mutation(false, columnAname, valueAname));
338    // mutations.add(new Mutation(false, columnBname, valueBname));
339    handler.mutateRow(tableAname, rowAname, getMutations(), null);
340
341    // Assert that the changes were made
342    assertEquals(valueAname, handler.get(tableAname, rowAname, columnAname, null).get(0).value);
343    TRowResult rowResult1 = handler.getRow(tableAname, rowAname, null).get(0);
344    assertEquals(rowAname, rowResult1.row);
345    assertEquals(valueBname, rowResult1.columns.get(columnBname).value);
346
347    // Apply a few BatchMutations for rowA and rowB
348    // rowAmutations.add(new Mutation(true, columnAname, null));
349    // rowAmutations.add(new Mutation(false, columnBname, valueCname));
350    // batchMutations.add(new BatchMutation(rowAname, rowAmutations));
351    // Mutations to rowB
352    // rowBmutations.add(new Mutation(false, columnAname, valueCname));
353    // rowBmutations.add(new Mutation(false, columnBname, valueDname));
354    // batchMutations.add(new BatchMutation(rowBname, rowBmutations));
355    handler.mutateRows(tableAname, getBatchMutations(), null);
356
357    // Assert that changes were made to rowA
358    List<TCell> cells = handler.get(tableAname, rowAname, columnAname, null);
359    assertFalse(cells.size() > 0);
360    assertEquals(valueCname, handler.get(tableAname, rowAname, columnBname, null).get(0).value);
361    List<TCell> versions = handler.getVer(tableAname, rowAname, columnBname, MAXVERSIONS, null);
362    assertEquals(valueCname, versions.get(0).value);
363    assertEquals(valueBname, versions.get(1).value);
364
365    // Assert that changes were made to rowB
366    TRowResult rowResult2 = handler.getRow(tableAname, rowBname, null).get(0);
367    assertEquals(rowBname, rowResult2.row);
368    assertEquals(valueCname, rowResult2.columns.get(columnAname).value);
369    assertEquals(valueDname, rowResult2.columns.get(columnBname).value);
370
371    // Apply some deletes
372    handler.deleteAll(tableAname, rowAname, columnBname, null);
373    handler.deleteAllRow(tableAname, rowBname, null);
374
375    // Assert that the deletes were applied
376    int size = handler.get(tableAname, rowAname, columnBname, null).size();
377    assertEquals(0, size);
378    size = handler.getRow(tableAname, rowBname, null).size();
379    assertEquals(0, size);
380
381    // Try null mutation
382    List<Mutation> mutations = new ArrayList<>(1);
383    mutations.add(new Mutation(false, columnAname, null, true));
384    handler.mutateRow(tableAname, rowAname, mutations, null);
385    TRowResult rowResult3 = handler.getRow(tableAname, rowAname, null).get(0);
386    assertEquals(rowAname, rowResult3.row);
387    assertEquals(0, rowResult3.columns.get(columnAname).value.remaining());
388
389    // Teardown
390    handler.disableTable(tableAname);
391    handler.deleteTable(tableAname);
392  }
393
394  /**
395   * Similar to testTableMutations(), except Mutations are applied with specific timestamps and data
396   * retrieval uses these timestamps to extract specific versions of data. n
397   */
398  public void doTestTableTimestampsAndColumns() throws Exception {
399    // Setup
400    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
401      UserProvider.instantiate(UTIL.getConfiguration()));
402    handler.createTable(tableAname, getColumnDescriptors());
403
404    // Apply timestamped Mutations to rowA
405    long time1 = EnvironmentEdgeManager.currentTime();
406    handler.mutateRowTs(tableAname, rowAname, getMutations(), time1, null);
407
408    Thread.sleep(1000);
409
410    // Apply timestamped BatchMutations for rowA and rowB
411    long time2 = EnvironmentEdgeManager.currentTime();
412    handler.mutateRowsTs(tableAname, getBatchMutations(), time2, null);
413
414    // Apply an overlapping timestamped mutation to rowB
415    handler.mutateRowTs(tableAname, rowBname, getMutations(), time2, null);
416
417    // the getVerTs is [inf, ts) so you need to increment one.
418    time1 += 1;
419    time2 += 2;
420
421    // Assert that the timestamp-related methods retrieve the correct data
422    assertEquals(2,
423      handler.getVerTs(tableAname, rowAname, columnBname, time2, MAXVERSIONS, null).size());
424    assertEquals(1,
425      handler.getVerTs(tableAname, rowAname, columnBname, time1, MAXVERSIONS, null).size());
426
427    TRowResult rowResult1 = handler.getRowTs(tableAname, rowAname, time1, null).get(0);
428    TRowResult rowResult2 = handler.getRowTs(tableAname, rowAname, time2, null).get(0);
429    // columnA was completely deleted
430    // assertTrue(Bytes.equals(rowResult1.columns.get(columnAname).value, valueAname));
431    assertEquals(rowResult1.columns.get(columnBname).value, valueBname);
432    assertEquals(rowResult2.columns.get(columnBname).value, valueCname);
433
434    // ColumnAname has been deleted, and will never be visible even with a getRowTs()
435    assertFalse(rowResult2.columns.containsKey(columnAname));
436
437    List<ByteBuffer> columns = new ArrayList<>(1);
438    columns.add(columnBname);
439
440    rowResult1 = handler.getRowWithColumns(tableAname, rowAname, columns, null).get(0);
441    assertEquals(rowResult1.columns.get(columnBname).value, valueCname);
442    assertFalse(rowResult1.columns.containsKey(columnAname));
443
444    rowResult1 = handler.getRowWithColumnsTs(tableAname, rowAname, columns, time1, null).get(0);
445    assertEquals(rowResult1.columns.get(columnBname).value, valueBname);
446    assertFalse(rowResult1.columns.containsKey(columnAname));
447
448    // Apply some timestamped deletes
449    // this actually deletes _everything_.
450    // nukes everything in columnB: forever.
451    handler.deleteAllTs(tableAname, rowAname, columnBname, time1, null);
452    handler.deleteAllRowTs(tableAname, rowBname, time2, null);
453
454    // Assert that the timestamp-related methods retrieve the correct data
455    int size = handler.getVerTs(tableAname, rowAname, columnBname, time1, MAXVERSIONS, null).size();
456    assertEquals(0, size);
457
458    size = handler.getVerTs(tableAname, rowAname, columnBname, time2, MAXVERSIONS, null).size();
459    assertEquals(1, size);
460
461    // should be available....
462    assertEquals(handler.get(tableAname, rowAname, columnBname, null).get(0).value, valueCname);
463
464    assertEquals(0, handler.getRow(tableAname, rowBname, null).size());
465
466    // Teardown
467    handler.disableTable(tableAname);
468    handler.deleteTable(tableAname);
469  }
470
471  /**
472   * Tests the four different scanner-opening methods (with and without a stoprow, with and without
473   * a timestamp). n
474   */
475  public void doTestTableScanners() throws Exception {
476    // Setup
477    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
478      UserProvider.instantiate(UTIL.getConfiguration()));
479    handler.createTable(tableAname, getColumnDescriptors());
480
481    // Apply timestamped Mutations to rowA
482    long time1 = EnvironmentEdgeManager.currentTime();
483    handler.mutateRowTs(tableAname, rowAname, getMutations(), time1, null);
484
485    // Sleep to assure that 'time1' and 'time2' will be different even with a
486    // coarse grained system timer.
487    Thread.sleep(1000);
488
489    // Apply timestamped BatchMutations for rowA and rowB
490    long time2 = EnvironmentEdgeManager.currentTime();
491    handler.mutateRowsTs(tableAname, getBatchMutations(), time2, null);
492
493    time1 += 1;
494
495    // Test a scanner on all rows and all columns, no timestamp
496    int scanner1 = handler.scannerOpen(tableAname, rowAname, getColumnList(true, true), null);
497    TRowResult rowResult1a = handler.scannerGet(scanner1).get(0);
498    assertEquals(rowResult1a.row, rowAname);
499    // This used to be '1'. I don't know why when we are asking for two columns
500    // and when the mutations above would seem to add two columns to the row.
501    // -- St.Ack 05/12/2009
502    assertEquals(1, rowResult1a.columns.size());
503    assertEquals(rowResult1a.columns.get(columnBname).value, valueCname);
504
505    TRowResult rowResult1b = handler.scannerGet(scanner1).get(0);
506    assertEquals(rowResult1b.row, rowBname);
507    assertEquals(2, rowResult1b.columns.size());
508    assertEquals(rowResult1b.columns.get(columnAname).value, valueCname);
509    assertEquals(rowResult1b.columns.get(columnBname).value, valueDname);
510    closeScanner(scanner1, handler);
511
512    // Test a scanner on all rows and all columns, with timestamp
513    int scanner2 =
514      handler.scannerOpenTs(tableAname, rowAname, getColumnList(true, true), time1, null);
515    TRowResult rowResult2a = handler.scannerGet(scanner2).get(0);
516    assertEquals(1, rowResult2a.columns.size());
517    // column A deleted, does not exist.
518    // assertTrue(Bytes.equals(rowResult2a.columns.get(columnAname).value, valueAname));
519    assertEquals(rowResult2a.columns.get(columnBname).value, valueBname);
520    closeScanner(scanner2, handler);
521
522    // Test a scanner on the first row and first column only, no timestamp
523    int scanner3 =
524      handler.scannerOpenWithStop(tableAname, rowAname, rowBname, getColumnList(true, false), null);
525    closeScanner(scanner3, handler);
526
527    // Test a scanner on the first row and second column only, with timestamp
528    int scanner4 = handler.scannerOpenWithStopTs(tableAname, rowAname, rowBname,
529      getColumnList(false, true), time1, null);
530    TRowResult rowResult4a = handler.scannerGet(scanner4).get(0);
531    assertEquals(1, rowResult4a.columns.size());
532    assertEquals(rowResult4a.columns.get(columnBname).value, valueBname);
533
534    // Test scanner using a TScan object once with sortColumns False and once with sortColumns true
535    TScan scanNoSortColumns = new TScan();
536    scanNoSortColumns.setStartRow(rowAname);
537    scanNoSortColumns.setStopRow(rowBname);
538
539    int scanner5 = handler.scannerOpenWithScan(tableAname, scanNoSortColumns, null);
540    TRowResult rowResult5 = handler.scannerGet(scanner5).get(0);
541    assertEquals(1, rowResult5.columns.size());
542    assertEquals(rowResult5.columns.get(columnBname).value, valueCname);
543
544    TScan scanSortColumns = new TScan();
545    scanSortColumns.setStartRow(rowAname);
546    scanSortColumns.setStopRow(rowBname);
547    scanSortColumns = scanSortColumns.setSortColumns(true);
548
549    int scanner6 = handler.scannerOpenWithScan(tableAname, scanSortColumns, null);
550    TRowResult rowResult6 = handler.scannerGet(scanner6).get(0);
551    assertEquals(1, rowResult6.sortedColumns.size());
552    assertEquals(rowResult6.sortedColumns.get(0).getCell().value, valueCname);
553
554    List<Mutation> rowBmutations = new ArrayList<>(20);
555    for (int i = 0; i < 20; i++) {
556      rowBmutations.add(new Mutation(false, asByteBuffer("columnA:" + i), valueCname, true));
557    }
558    ByteBuffer rowC = asByteBuffer("rowC");
559    handler.mutateRow(tableAname, rowC, rowBmutations, null);
560
561    TScan scanSortMultiColumns = new TScan();
562    scanSortMultiColumns.setStartRow(rowC);
563    scanSortMultiColumns = scanSortMultiColumns.setSortColumns(true);
564    int scanner7 = handler.scannerOpenWithScan(tableAname, scanSortMultiColumns, null);
565    TRowResult rowResult7 = handler.scannerGet(scanner7).get(0);
566
567    ByteBuffer smallerColumn = asByteBuffer("columnA:");
568    for (int i = 0; i < 20; i++) {
569      ByteBuffer currentColumn = rowResult7.sortedColumns.get(i).columnName;
570      assertTrue(Bytes.compareTo(smallerColumn.array(), currentColumn.array()) < 0);
571      smallerColumn = currentColumn;
572    }
573
574    TScan reversedScan = new TScan();
575    reversedScan.setReversed(true);
576    reversedScan.setStartRow(rowBname);
577    reversedScan.setStopRow(rowAname);
578
579    int scanner8 = handler.scannerOpenWithScan(tableAname, reversedScan, null);
580    List<TRowResult> results = handler.scannerGet(scanner8);
581    handler.scannerClose(scanner8);
582    assertEquals(1, results.size());
583    assertEquals(ByteBuffer.wrap(results.get(0).getRow()), rowBname);
584
585    // Teardown
586    handler.disableTable(tableAname);
587    handler.deleteTable(tableAname);
588  }
589
590  /**
591   * For HBASE-2556 Tests for GetTableRegions n
592   */
593  public void doTestGetTableRegions() throws Exception {
594    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
595      UserProvider.instantiate(UTIL.getConfiguration()));
596    doTestGetTableRegions(handler);
597  }
598
599  public static void doTestGetTableRegions(Hbase.Iface handler) throws Exception {
600    assertEquals(0, handler.getTableNames().size());
601    handler.createTable(tableAname, getColumnDescriptors());
602    assertEquals(1, handler.getTableNames().size());
603    List<TRegionInfo> regions = handler.getTableRegions(tableAname);
604    int regionCount = regions.size();
605    assertEquals("empty table should have only 1 region, " + "but found " + regionCount, 1,
606      regionCount);
607    LOG.info("Region found:" + regions.get(0));
608    handler.disableTable(tableAname);
609    handler.deleteTable(tableAname);
610    regionCount = handler.getTableRegions(tableAname).size();
611    assertEquals("non-existing table should have 0 region, " + "but found " + regionCount, 0,
612      regionCount);
613  }
614
615  public void doTestFilterRegistration() throws Exception {
616    Configuration conf = UTIL.getConfiguration();
617
618    conf.set("hbase.thrift.filters", "MyFilter:filterclass");
619
620    ThriftServer.registerFilters(conf);
621
622    Map<String, String> registeredFilters = ParseFilter.getAllFilters();
623
624    assertEquals("filterclass", registeredFilters.get("MyFilter"));
625  }
626
627  public void doTestGetRegionInfo() throws Exception {
628    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
629      UserProvider.instantiate(UTIL.getConfiguration()));
630    doTestGetRegionInfo(handler);
631  }
632
633  public static void doTestGetRegionInfo(Hbase.Iface handler) throws Exception {
634    // Create tableA and add two columns to rowA
635    handler.createTable(tableAname, getColumnDescriptors());
636    try {
637      handler.mutateRow(tableAname, rowAname, getMutations(), null);
638      byte[] searchRow = HRegionInfo.createRegionName(TableName.valueOf(tableAname.array()),
639        rowAname.array(), HConstants.NINES, false);
640      TRegionInfo regionInfo = handler.getRegionInfo(ByteBuffer.wrap(searchRow));
641      assertTrue(
642        Bytes.toStringBinary(regionInfo.getName()).startsWith(Bytes.toStringBinary(tableAname)));
643    } finally {
644      handler.disableTable(tableAname);
645      handler.deleteTable(tableAname);
646    }
647  }
648
649  /**
650   * Appends the value to a cell and checks that the cell value is updated properly. n
651   */
652  public static void doTestAppend() throws Exception {
653    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
654      UserProvider.instantiate(UTIL.getConfiguration()));
655    handler.createTable(tableAname, getColumnDescriptors());
656    try {
657      List<Mutation> mutations = new ArrayList<>(1);
658      mutations.add(new Mutation(false, columnAname, valueAname, true));
659      handler.mutateRow(tableAname, rowAname, mutations, null);
660
661      List<ByteBuffer> columnList = new ArrayList<>(1);
662      columnList.add(columnAname);
663      List<ByteBuffer> valueList = new ArrayList<>(1);
664      valueList.add(valueBname);
665
666      TAppend append = new TAppend(tableAname, rowAname, columnList, valueList);
667      handler.append(append);
668
669      TRowResult rowResult = handler.getRow(tableAname, rowAname, null).get(0);
670      assertEquals(rowAname, rowResult.row);
671      assertArrayEquals(Bytes.add(valueAname.array(), valueBname.array()),
672        rowResult.columns.get(columnAname).value.array());
673    } finally {
674      handler.disableTable(tableAname);
675      handler.deleteTable(tableAname);
676    }
677  }
678
679  /**
680   * Check that checkAndPut fails if the cell does not exist, then put in the cell, then check that
681   * the checkAndPut succeeds. n
682   */
683  public static void doTestCheckAndPut() throws Exception {
684    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
685      UserProvider.instantiate(UTIL.getConfiguration()));
686    handler.createTable(tableAname, getColumnDescriptors());
687    try {
688      List<Mutation> mutations = new ArrayList<>(1);
689      mutations.add(new Mutation(false, columnAname, valueAname, true));
690      Mutation putB = (new Mutation(false, columnBname, valueBname, true));
691
692      assertFalse(handler.checkAndPut(tableAname, rowAname, columnAname, valueAname, putB, null));
693
694      handler.mutateRow(tableAname, rowAname, mutations, null);
695
696      assertTrue(handler.checkAndPut(tableAname, rowAname, columnAname, valueAname, putB, null));
697
698      TRowResult rowResult = handler.getRow(tableAname, rowAname, null).get(0);
699      assertEquals(rowAname, rowResult.row);
700      assertEquals(valueBname, rowResult.columns.get(columnBname).value);
701    } finally {
702      handler.disableTable(tableAname);
703      handler.deleteTable(tableAname);
704    }
705  }
706
707  @Test
708  public void testGetTableNamesWithStatus() throws Exception {
709    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
710      UserProvider.instantiate(UTIL.getConfiguration()));
711
712    createTestTables(handler);
713
714    assertEquals(2, handler.getTableNamesWithIsTableEnabled().size());
715    assertEquals(2, countTablesByStatus(true, handler));
716    handler.disableTable(tableBname);
717    assertEquals(1, countTablesByStatus(true, handler));
718    assertEquals(1, countTablesByStatus(false, handler));
719    assertEquals(2, handler.getTableNamesWithIsTableEnabled().size());
720    handler.enableTable(tableBname);
721    assertEquals(2, countTablesByStatus(true, handler));
722
723    dropTestTables(handler);
724  }
725
726  private static int countTablesByStatus(Boolean isEnabled, Hbase.Iface handler) throws Exception {
727    AtomicInteger counter = new AtomicInteger(0);
728    handler.getTableNamesWithIsTableEnabled().forEach((table, tableStatus) -> {
729      if (tableStatus.equals(isEnabled)) counter.getAndIncrement();
730    });
731    return counter.get();
732  }
733
734  @Test
735  public void testMetricsWithException() throws Exception {
736    String rowkey = "row1";
737    String family = "f";
738    String col = "c";
739    // create a table which will throw exceptions for requests
740    final TableName tableName = TableName.valueOf(name.getMethodName());
741    try {
742      HTableDescriptor tableDesc = new HTableDescriptor(tableName);
743      tableDesc.addCoprocessor(ErrorThrowingGetObserver.class.getName());
744      tableDesc.addFamily(new HColumnDescriptor(family));
745
746      Table table = UTIL.createTable(tableDesc, null);
747      long now = EnvironmentEdgeManager.currentTime();
748      table.put(new Put(Bytes.toBytes(rowkey)).addColumn(Bytes.toBytes(family), Bytes.toBytes(col),
749        now, Bytes.toBytes("val1")));
750
751      Configuration conf = UTIL.getConfiguration();
752      ThriftMetrics metrics = getMetrics(conf);
753      ThriftHBaseServiceHandler hbaseHandler = new ThriftHBaseServiceHandler(
754        UTIL.getConfiguration(), UserProvider.instantiate(UTIL.getConfiguration()));
755      Hbase.Iface handler = HbaseHandlerMetricsProxy.newInstance(hbaseHandler, metrics, conf);
756
757      ByteBuffer tTableName = asByteBuffer(tableName.getNameAsString());
758
759      // check metrics increment with a successful get
760      long preGetCounter = metricsHelper.checkCounterExists("getRow_num_ops", metrics.getSource())
761        ? metricsHelper.getCounter("getRow_num_ops", metrics.getSource())
762        : 0;
763      List<TRowResult> tRowResult = handler.getRow(tTableName, asByteBuffer(rowkey), null);
764      assertEquals(1, tRowResult.size());
765      TRowResult tResult = tRowResult.get(0);
766
767      TCell expectedColumnValue = new TCell(asByteBuffer("val1"), now);
768
769      assertArrayEquals(Bytes.toBytes(rowkey), tResult.getRow());
770      Collection<TCell> returnedColumnValues = tResult.getColumns().values();
771      assertEquals(1, returnedColumnValues.size());
772      assertEquals(expectedColumnValue, returnedColumnValues.iterator().next());
773
774      metricsHelper.assertCounter("getRow_num_ops", preGetCounter + 1, metrics.getSource());
775
776      // check metrics increment when the get throws each exception type
777      for (ErrorThrowingGetObserver.ErrorType type : ErrorThrowingGetObserver.ErrorType.values()) {
778        testExceptionType(handler, metrics, tTableName, rowkey, type);
779      }
780    } finally {
781      UTIL.deleteTable(tableName);
782    }
783  }
784
785  private void testExceptionType(Hbase.Iface handler, ThriftMetrics metrics, ByteBuffer tTableName,
786    String rowkey, ErrorThrowingGetObserver.ErrorType errorType) throws Exception {
787    long preGetCounter = metricsHelper.getCounter("getRow_num_ops", metrics.getSource());
788    String exceptionKey = errorType.getMetricName();
789    long preExceptionCounter = metricsHelper.checkCounterExists(exceptionKey, metrics.getSource())
790      ? metricsHelper.getCounter(exceptionKey, metrics.getSource())
791      : 0;
792    Map<ByteBuffer, ByteBuffer> attributes = new HashMap<>();
793    attributes.put(asByteBuffer(ErrorThrowingGetObserver.SHOULD_ERROR_ATTRIBUTE),
794      asByteBuffer(errorType.name()));
795    try {
796      List<TRowResult> tRowResult = handler.getRow(tTableName, asByteBuffer(rowkey), attributes);
797      fail("Get with error attribute should have thrown an exception");
798    } catch (IOError e) {
799      LOG.info("Received exception: ", e);
800      metricsHelper.assertCounter("getRow_num_ops", preGetCounter + 1, metrics.getSource());
801      metricsHelper.assertCounter(exceptionKey, preExceptionCounter + 1, metrics.getSource());
802    }
803  }
804
805  /**
806   * @return a List of ColumnDescriptors for use in creating a table. Has one default
807   *         ColumnDescriptor and one ColumnDescriptor with fewer versions
808   */
809  private static List<ColumnDescriptor> getColumnDescriptors() {
810    ArrayList<ColumnDescriptor> cDescriptors = new ArrayList<>(2);
811
812    // A default ColumnDescriptor
813    ColumnDescriptor cDescA = new ColumnDescriptor();
814    cDescA.name = columnAname;
815    cDescriptors.add(cDescA);
816
817    // A slightly customized ColumnDescriptor (only 2 versions)
818    ColumnDescriptor cDescB =
819      new ColumnDescriptor(columnBname, 2, "NONE", false, "NONE", 0, 0, false, -1);
820    cDescriptors.add(cDescB);
821
822    return cDescriptors;
823  }
824
825  /**
826   * @param includeA whether or not to include columnA
827   * @param includeB whether or not to include columnB
828   * @return a List of column names for use in retrieving a scanner
829   */
830  private List<ByteBuffer> getColumnList(boolean includeA, boolean includeB) {
831    List<ByteBuffer> columnList = new ArrayList<>();
832    if (includeA) columnList.add(columnAname);
833    if (includeB) columnList.add(columnBname);
834    return columnList;
835  }
836
837  /** Returns a List of Mutations for a row, with columnA having valueA and columnB having valueB */
838  private static List<Mutation> getMutations() {
839    List<Mutation> mutations = new ArrayList<>(2);
840    mutations.add(new Mutation(false, columnAname, valueAname, true));
841    mutations.add(new Mutation(false, columnBname, valueBname, true));
842    return mutations;
843  }
844
845  /**
846   * @return a List of BatchMutations with the following effects: (rowA, columnA): delete (rowA,
847   *         columnB): place valueC (rowB, columnA): place valueC (rowB, columnB): place valueD
848   */
849  private static List<BatchMutation> getBatchMutations() {
850    List<BatchMutation> batchMutations = new ArrayList<>(3);
851
852    // Mutations to rowA. You can't mix delete and put anymore.
853    List<Mutation> rowAmutations = new ArrayList<>(1);
854    rowAmutations.add(new Mutation(true, columnAname, null, true));
855    batchMutations.add(new BatchMutation(rowAname, rowAmutations));
856
857    rowAmutations = new ArrayList<>(1);
858    rowAmutations.add(new Mutation(false, columnBname, valueCname, true));
859    batchMutations.add(new BatchMutation(rowAname, rowAmutations));
860
861    // Mutations to rowB
862    List<Mutation> rowBmutations = new ArrayList<>(2);
863    rowBmutations.add(new Mutation(false, columnAname, valueCname, true));
864    rowBmutations.add(new Mutation(false, columnBname, valueDname, true));
865    batchMutations.add(new BatchMutation(rowBname, rowBmutations));
866
867    return batchMutations;
868  }
869
870  /**
871   * Asserts that the passed scanner is exhausted, and then closes the scanner.
872   * @param scannerId the scanner to close
873   * @param handler   the HBaseServiceHandler interfacing to HBase n
874   */
875  private void closeScanner(int scannerId, ThriftHBaseServiceHandler handler) throws Exception {
876    handler.scannerGet(scannerId);
877    handler.scannerClose(scannerId);
878  }
879
880  @Test
881  public void testGetThriftServerType() throws Exception {
882    ThriftHBaseServiceHandler handler = new ThriftHBaseServiceHandler(UTIL.getConfiguration(),
883      UserProvider.instantiate(UTIL.getConfiguration()));
884    assertEquals(TThriftServerType.ONE, handler.getThriftServerType());
885  }
886
887  /**
888   * Verify that thrift client calling thrift2 server can get the thrift2 server type correctly.
889   */
890  @Test
891  public void testGetThriftServerOneType() throws Exception {
892    // start a thrift2 server
893    HBaseThriftTestingUtility THRIFT_TEST_UTIL = new HBaseThriftTestingUtility();
894
895    LOG.info("Starting HBase Thrift Server Two");
896    THRIFT_TEST_UTIL.startThriftServer(UTIL.getConfiguration(), ThriftServerType.TWO);
897    try (TTransport transport =
898      new TSocket(InetAddress.getLocalHost().getHostName(), THRIFT_TEST_UTIL.getServerPort())) {
899      TProtocol protocol = new TBinaryProtocol(transport);
900      // This is our thrift client.
901      Hbase.Client client = new Hbase.Client(protocol);
902      // open the transport
903      transport.open();
904      assertEquals(TThriftServerType.TWO.name(), client.getThriftServerType().name());
905    } finally {
906      THRIFT_TEST_UTIL.stopThriftServer();
907    }
908  }
909}