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