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