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