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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collections;
030import java.util.List;
031import java.util.Optional;
032import java.util.Random;
033import java.util.concurrent.CountDownLatch;
034import java.util.concurrent.ExecutorService;
035import java.util.concurrent.Executors;
036import java.util.concurrent.TimeUnit;
037import java.util.concurrent.atomic.AtomicInteger;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.hbase.Cell;
040import org.apache.hadoop.hbase.CellUtil;
041import org.apache.hadoop.hbase.Coprocessor;
042import org.apache.hadoop.hbase.HBaseClassTestRule;
043import org.apache.hadoop.hbase.HBaseTestingUtility;
044import org.apache.hadoop.hbase.HColumnDescriptor;
045import org.apache.hadoop.hbase.HConstants;
046import org.apache.hadoop.hbase.HRegionLocation;
047import org.apache.hadoop.hbase.HTableDescriptor;
048import org.apache.hadoop.hbase.TableName;
049import org.apache.hadoop.hbase.coprocessor.MultiRowMutationEndpoint;
050import org.apache.hadoop.hbase.coprocessor.ObserverContext;
051import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor;
052import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
053import org.apache.hadoop.hbase.coprocessor.RegionObserver;
054import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
055import org.apache.hadoop.hbase.ipc.ServerRpcController;
056import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos;
057import org.apache.hadoop.hbase.regionserver.HRegion;
058import org.apache.hadoop.hbase.regionserver.HRegionServer;
059import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress;
060import org.apache.hadoop.hbase.regionserver.RegionScanner;
061import org.apache.hadoop.hbase.testclassification.ClientTests;
062import org.apache.hadoop.hbase.testclassification.LargeTests;
063import org.apache.hadoop.hbase.util.Bytes;
064import org.apache.hadoop.hbase.util.Pair;
065import org.junit.After;
066import org.junit.AfterClass;
067import org.junit.Before;
068import org.junit.BeforeClass;
069import org.junit.ClassRule;
070import org.junit.Rule;
071import org.junit.Test;
072import org.junit.experimental.categories.Category;
073import org.junit.rules.TestName;
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
078import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
079
080@Category({LargeTests.class, ClientTests.class})
081public class TestFromClientSide3 {
082
083  @ClassRule
084  public static final HBaseClassTestRule CLASS_RULE =
085      HBaseClassTestRule.forClass(TestFromClientSide3.class);
086
087  private static final Logger LOG = LoggerFactory.getLogger(TestFromClientSide3.class);
088  private final static HBaseTestingUtility TEST_UTIL
089    = new HBaseTestingUtility();
090  private static byte[] FAMILY = Bytes.toBytes("testFamily");
091  private static Random random = new Random();
092  private static int SLAVES = 3;
093  private static final byte[] ROW = Bytes.toBytes("testRow");
094  private static final byte[] ANOTHERROW = Bytes.toBytes("anotherrow");
095  private static final byte[] QUALIFIER = Bytes.toBytes("testQualifier");
096  private static final byte[] VALUE = Bytes.toBytes("testValue");
097  private static final byte[] COL_QUAL = Bytes.toBytes("f1");
098  private static final byte[] VAL_BYTES = Bytes.toBytes("v1");
099  private static final byte[] ROW_BYTES = Bytes.toBytes("r1");
100
101  @Rule
102  public TestName name = new TestName();
103
104  /**
105   * @throws java.lang.Exception
106   */
107  @BeforeClass
108  public static void setUpBeforeClass() throws Exception {
109    TEST_UTIL.startMiniCluster(SLAVES);
110  }
111
112  /**
113   * @throws java.lang.Exception
114   */
115  @AfterClass
116  public static void tearDownAfterClass() throws Exception {
117    TEST_UTIL.shutdownMiniCluster();
118  }
119
120  /**
121   * @throws java.lang.Exception
122   */
123  @Before
124  public void setUp() throws Exception {
125    // Nothing to do.
126  }
127
128  /**
129   * @throws java.lang.Exception
130   */
131  @After
132  public void tearDown() throws Exception {
133    for (HTableDescriptor htd: TEST_UTIL.getAdmin().listTables()) {
134      LOG.info("Tear down, remove table=" + htd.getTableName());
135      TEST_UTIL.deleteTable(htd.getTableName());
136  }
137  }
138
139  private void randomCFPuts(Table table, byte[] row, byte[] family, int nPuts)
140      throws Exception {
141    Put put = new Put(row);
142    for (int i = 0; i < nPuts; i++) {
143      byte[] qualifier = Bytes.toBytes(random.nextInt());
144      byte[] value = Bytes.toBytes(random.nextInt());
145      put.addColumn(family, qualifier, value);
146    }
147    table.put(put);
148  }
149
150  private void performMultiplePutAndFlush(HBaseAdmin admin, Table table,
151      byte[] row, byte[] family, int nFlushes, int nPuts)
152  throws Exception {
153
154    try (RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(table.getName())) {
155      // connection needed for poll-wait
156      HRegionLocation loc = locator.getRegionLocation(row, true);
157      AdminProtos.AdminService.BlockingInterface server =
158        ((ClusterConnection) admin.getConnection()).getAdmin(loc.getServerName());
159      byte[] regName = loc.getRegionInfo().getRegionName();
160
161      for (int i = 0; i < nFlushes; i++) {
162        randomCFPuts(table, row, family, nPuts);
163        List<String> sf = ProtobufUtil.getStoreFiles(server, regName, FAMILY);
164        int sfCount = sf.size();
165
166        admin.flush(table.getName());
167      }
168    }
169  }
170
171  private static List<Cell> toList(ResultScanner scanner) {
172    try {
173      List<Cell> cells = new ArrayList<>();
174      for (Result r : scanner) {
175        cells.addAll(r.listCells());
176      }
177      return cells;
178    } finally {
179      scanner.close();
180    }
181  }
182
183  @Test
184  public void testScanAfterDeletingSpecifiedRow() throws IOException {
185    TableName tableName = TableName.valueOf(name.getMethodName());
186    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
187            .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY))
188            .build();
189    TEST_UTIL.getAdmin().createTable(desc);
190    byte[] row = Bytes.toBytes("SpecifiedRow");
191    byte[] value0 = Bytes.toBytes("value_0");
192    byte[] value1 = Bytes.toBytes("value_1");
193    try (Table t = TEST_UTIL.getConnection().getTable(tableName)) {
194      Put put = new Put(row);
195      put.addColumn(FAMILY, QUALIFIER, VALUE);
196      t.put(put);
197      Delete d = new Delete(row);
198      t.delete(d);
199      put = new Put(row);
200      put.addColumn(FAMILY, null, value0);
201      t.put(put);
202      put = new Put(row);
203      put.addColumn(FAMILY, null, value1);
204      t.put(put);
205      List<Cell> cells = toList(t.getScanner(new Scan()));
206      assertEquals(1, cells.size());
207      assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0))));
208
209      cells = toList(t.getScanner(new Scan().addFamily(FAMILY)));
210      assertEquals(1, cells.size());
211      assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0))));
212
213      cells = toList(t.getScanner(new Scan().addColumn(FAMILY, QUALIFIER)));
214      assertEquals(0, cells.size());
215
216      TEST_UTIL.getAdmin().flush(tableName);
217      cells = toList(t.getScanner(new Scan()));
218      assertEquals(1, cells.size());
219      assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0))));
220
221      cells = toList(t.getScanner(new Scan().addFamily(FAMILY)));
222      assertEquals(1, cells.size());
223      assertEquals("value_1", Bytes.toString(CellUtil.cloneValue(cells.get(0))));
224
225      cells = toList(t.getScanner(new Scan().addColumn(FAMILY, QUALIFIER)));
226      assertEquals(0, cells.size());
227    }
228  }
229
230  @Test
231  public void testScanAfterDeletingSpecifiedRowV2() throws IOException {
232    TableName tableName = TableName.valueOf(name.getMethodName());
233    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
234            .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY))
235            .build();
236    TEST_UTIL.getAdmin().createTable(desc);
237    byte[] row = Bytes.toBytes("SpecifiedRow");
238    byte[] qual0 = Bytes.toBytes("qual0");
239    byte[] qual1 = Bytes.toBytes("qual1");
240    try (Table t = TEST_UTIL.getConnection().getTable(tableName)) {
241      Delete d = new Delete(row);
242      t.delete(d);
243
244      Put put = new Put(row);
245      put.addColumn(FAMILY, null, VALUE);
246      t.put(put);
247
248      put = new Put(row);
249      put.addColumn(FAMILY, qual1, qual1);
250      t.put(put);
251
252      put = new Put(row);
253      put.addColumn(FAMILY, qual0, qual0);
254      t.put(put);
255
256      Result r = t.get(new Get(row));
257      assertEquals(3, r.size());
258      assertEquals("testValue", Bytes.toString(CellUtil.cloneValue(r.rawCells()[0])));
259      assertEquals("qual0", Bytes.toString(CellUtil.cloneValue(r.rawCells()[1])));
260      assertEquals("qual1", Bytes.toString(CellUtil.cloneValue(r.rawCells()[2])));
261
262      TEST_UTIL.getAdmin().flush(tableName);
263      r = t.get(new Get(row));
264      assertEquals(3, r.size());
265      assertEquals("testValue", Bytes.toString(CellUtil.cloneValue(r.rawCells()[0])));
266      assertEquals("qual0", Bytes.toString(CellUtil.cloneValue(r.rawCells()[1])));
267      assertEquals("qual1", Bytes.toString(CellUtil.cloneValue(r.rawCells()[2])));
268    }
269  }
270
271  // override the config settings at the CF level and ensure priority
272  @Test
273  public void testAdvancedConfigOverride() throws Exception {
274    /*
275     * Overall idea: (1) create 3 store files and issue a compaction. config's
276     * compaction.min == 3, so should work. (2) Increase the compaction.min
277     * toggle in the HTD to 5 and modify table. If we use the HTD value instead
278     * of the default config value, adding 3 files and issuing a compaction
279     * SHOULD NOT work (3) Decrease the compaction.min toggle in the HCD to 2
280     * and modify table. The CF schema should override the Table schema and now
281     * cause a minor compaction.
282     */
283    TEST_UTIL.getConfiguration().setInt("hbase.hstore.compaction.min", 3);
284
285    final TableName tableName = TableName.valueOf(name.getMethodName());
286    Table hTable = TEST_UTIL.createTable(tableName, FAMILY, 10);
287    Admin admin = TEST_UTIL.getAdmin();
288    ClusterConnection connection = (ClusterConnection) TEST_UTIL.getConnection();
289
290    // Create 3 store files.
291    byte[] row = Bytes.toBytes(random.nextInt());
292    performMultiplePutAndFlush((HBaseAdmin) admin, hTable, row, FAMILY, 3, 100);
293
294    try (RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(tableName)) {
295      // Verify we have multiple store files.
296      HRegionLocation loc = locator.getRegionLocation(row, true);
297      byte[] regionName = loc.getRegionInfo().getRegionName();
298      AdminProtos.AdminService.BlockingInterface server = connection.getAdmin(loc.getServerName());
299      assertTrue(ProtobufUtil.getStoreFiles(server, regionName, FAMILY).size() > 1);
300
301      // Issue a compaction request
302      admin.compact(tableName);
303
304      // poll wait for the compactions to happen
305      for (int i = 0; i < 10 * 1000 / 40; ++i) {
306        // The number of store files after compaction should be lesser.
307        loc = locator.getRegionLocation(row, true);
308        if (!loc.getRegionInfo().isOffline()) {
309          regionName = loc.getRegionInfo().getRegionName();
310          server = connection.getAdmin(loc.getServerName());
311          if (ProtobufUtil.getStoreFiles(server, regionName, FAMILY).size() <= 1) {
312            break;
313          }
314        }
315        Thread.sleep(40);
316      }
317      // verify the compactions took place and that we didn't just time out
318      assertTrue(ProtobufUtil.getStoreFiles(server, regionName, FAMILY).size() <= 1);
319
320      // change the compaction.min config option for this table to 5
321      LOG.info("hbase.hstore.compaction.min should now be 5");
322      HTableDescriptor htd = new HTableDescriptor(hTable.getTableDescriptor());
323      htd.setValue("hbase.hstore.compaction.min", String.valueOf(5));
324      admin.modifyTable(tableName, htd);
325      Pair<Integer, Integer> st;
326      while (null != (st = admin.getAlterStatus(tableName)) && st.getFirst() > 0) {
327        LOG.debug(st.getFirst() + " regions left to update");
328        Thread.sleep(40);
329      }
330      LOG.info("alter status finished");
331
332      // Create 3 more store files.
333      performMultiplePutAndFlush((HBaseAdmin) admin, hTable, row, FAMILY, 3, 10);
334
335      // Issue a compaction request
336      admin.compact(tableName);
337
338      // This time, the compaction request should not happen
339      Thread.sleep(10 * 1000);
340      loc = locator.getRegionLocation(row, true);
341      regionName = loc.getRegionInfo().getRegionName();
342      server = connection.getAdmin(loc.getServerName());
343      int sfCount = ProtobufUtil.getStoreFiles(server, regionName, FAMILY).size();
344      assertTrue(sfCount > 1);
345
346      // change an individual CF's config option to 2 & online schema update
347      LOG.info("hbase.hstore.compaction.min should now be 2");
348      HColumnDescriptor hcd = new HColumnDescriptor(htd.getFamily(FAMILY));
349      hcd.setValue("hbase.hstore.compaction.min", String.valueOf(2));
350      htd.modifyFamily(hcd);
351      admin.modifyTable(tableName, htd);
352      while (null != (st = admin.getAlterStatus(tableName)) && st.getFirst() > 0) {
353        LOG.debug(st.getFirst() + " regions left to update");
354        Thread.sleep(40);
355      }
356      LOG.info("alter status finished");
357
358      // Issue a compaction request
359      admin.compact(tableName);
360
361      // poll wait for the compactions to happen
362      for (int i = 0; i < 10 * 1000 / 40; ++i) {
363        loc = locator.getRegionLocation(row, true);
364        regionName = loc.getRegionInfo().getRegionName();
365        try {
366          server = connection.getAdmin(loc.getServerName());
367          if (ProtobufUtil.getStoreFiles(server, regionName, FAMILY).size() < sfCount) {
368            break;
369          }
370        } catch (Exception e) {
371          LOG.debug("Waiting for region to come online: " + Bytes.toString(regionName));
372        }
373        Thread.sleep(40);
374      }
375
376      // verify the compaction took place and that we didn't just time out
377      assertTrue(ProtobufUtil.getStoreFiles(
378        server, regionName, FAMILY).size() < sfCount);
379
380      // Finally, ensure that we can remove a custom config value after we made it
381      LOG.info("Removing CF config value");
382      LOG.info("hbase.hstore.compaction.min should now be 5");
383      hcd = new HColumnDescriptor(htd.getFamily(FAMILY));
384      hcd.setValue("hbase.hstore.compaction.min", null);
385      htd.modifyFamily(hcd);
386      admin.modifyTable(tableName, htd);
387      while (null != (st = admin.getAlterStatus(tableName)) && st.getFirst() > 0) {
388        LOG.debug(st.getFirst() + " regions left to update");
389        Thread.sleep(40);
390      }
391      LOG.info("alter status finished");
392      assertNull(hTable.getTableDescriptor().getFamily(FAMILY).getValue(
393          "hbase.hstore.compaction.min"));
394    }
395  }
396
397  @Test
398  public void testHTableBatchWithEmptyPut ()throws Exception {
399      Table table = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()),
400          new byte[][] { FAMILY });
401    try {
402      List actions = (List) new ArrayList();
403      Object[] results = new Object[2];
404      // create an empty Put
405      Put put1 = new Put(ROW);
406      actions.add(put1);
407
408      Put put2 = new Put(ANOTHERROW);
409      put2.addColumn(FAMILY, QUALIFIER, VALUE);
410      actions.add(put2);
411
412      table.batch(actions, results);
413      fail("Empty Put should have failed the batch call");
414    } catch (IllegalArgumentException iae) {
415
416    } finally {
417      table.close();
418    }
419  }
420
421  // Test Table.batch with large amount of mutations against the same key.
422  // It used to trigger read lock's "Maximum lock count exceeded" Error.
423  @Test
424  public void testHTableWithLargeBatch() throws Exception {
425    Table table = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()),
426        new byte[][] { FAMILY });
427    int sixtyFourK = 64 * 1024;
428    try {
429      List actions = new ArrayList();
430      Object[] results = new Object[(sixtyFourK + 1) * 2];
431
432      for (int i = 0; i < sixtyFourK + 1; i ++) {
433        Put put1 = new Put(ROW);
434        put1.addColumn(FAMILY, QUALIFIER, VALUE);
435        actions.add(put1);
436
437        Put put2 = new Put(ANOTHERROW);
438        put2.addColumn(FAMILY, QUALIFIER, VALUE);
439        actions.add(put2);
440      }
441
442      table.batch(actions, results);
443    } finally {
444      table.close();
445    }
446  }
447
448  @Test
449  public void testBatchWithRowMutation() throws Exception {
450    LOG.info("Starting testBatchWithRowMutation");
451    final TableName TABLENAME = TableName.valueOf("testBatchWithRowMutation");
452    try (Table t = TEST_UTIL.createTable(TABLENAME, FAMILY)) {
453      byte [][] QUALIFIERS = new byte [][] {
454        Bytes.toBytes("a"), Bytes.toBytes("b")
455      };
456
457      RowMutations arm = RowMutations.of(Collections.singletonList(
458        new Put(ROW).addColumn(FAMILY, QUALIFIERS[0], VALUE)));
459      Object[] batchResult = new Object[1];
460      t.batch(Arrays.asList(arm), batchResult);
461
462      Get g = new Get(ROW);
463      Result r = t.get(g);
464      assertEquals(0, Bytes.compareTo(VALUE, r.getValue(FAMILY, QUALIFIERS[0])));
465
466      arm = RowMutations.of(Arrays.asList(
467        new Put(ROW).addColumn(FAMILY, QUALIFIERS[1], VALUE),
468        new Delete(ROW).addColumns(FAMILY, QUALIFIERS[0])));
469      t.batch(Arrays.asList(arm), batchResult);
470      r = t.get(g);
471      assertEquals(0, Bytes.compareTo(VALUE, r.getValue(FAMILY, QUALIFIERS[1])));
472      assertNull(r.getValue(FAMILY, QUALIFIERS[0]));
473
474      // Test that we get the correct remote exception for RowMutations from batch()
475      try {
476        arm = RowMutations.of(Collections.singletonList(
477          new Put(ROW).addColumn(new byte[]{'b', 'o', 'g', 'u', 's'}, QUALIFIERS[0], VALUE)));
478        t.batch(Arrays.asList(arm), batchResult);
479        fail("Expected RetriesExhaustedWithDetailsException with NoSuchColumnFamilyException");
480      } catch(RetriesExhaustedWithDetailsException e) {
481        String msg = e.getMessage();
482        assertTrue(msg.contains("NoSuchColumnFamilyException"));
483      }
484    }
485  }
486
487  @Test
488  public void testHTableExistsMethodSingleRegionSingleGet() throws Exception {
489      // Test with a single region table.
490      Table table = TEST_UTIL.createTable(
491          TableName.valueOf(name.getMethodName()),
492          new byte[][] { FAMILY });
493
494    Put put = new Put(ROW);
495    put.addColumn(FAMILY, QUALIFIER, VALUE);
496
497    Get get = new Get(ROW);
498
499    boolean exist = table.exists(get);
500    assertFalse(exist);
501
502    table.put(put);
503
504    exist = table.exists(get);
505    assertTrue(exist);
506  }
507
508  @Test
509  public void testHTableExistsMethodSingleRegionMultipleGets() throws Exception {
510    Table table = TEST_UTIL.createTable(TableName.valueOf(
511        name.getMethodName()), new byte[][] { FAMILY });
512
513    Put put = new Put(ROW);
514    put.addColumn(FAMILY, QUALIFIER, VALUE);
515    table.put(put);
516
517    List<Get> gets = new ArrayList<>();
518    gets.add(new Get(ROW));
519    gets.add(new Get(ANOTHERROW));
520
521    boolean[] results = table.exists(gets);
522    assertTrue(results[0]);
523    assertFalse(results[1]);
524  }
525
526  @Test
527  public void testHTableExistsBeforeGet() throws Exception {
528    Table table = TEST_UTIL.createTable(TableName.valueOf(name.getMethodName()),
529        new byte[][] { FAMILY });
530    try {
531      Put put = new Put(ROW);
532      put.addColumn(FAMILY, QUALIFIER, VALUE);
533      table.put(put);
534
535      Get get = new Get(ROW);
536
537      boolean exist = table.exists(get);
538      assertEquals(true, exist);
539
540      Result result = table.get(get);
541      assertEquals(false, result.isEmpty());
542      assertTrue(Bytes.equals(VALUE, result.getValue(FAMILY, QUALIFIER)));
543    } finally {
544      table.close();
545    }
546  }
547
548  @Test
549  public void testHTableExistsAllBeforeGet() throws Exception {
550    final byte[] ROW2 = Bytes.add(ROW, Bytes.toBytes("2"));
551    Table table = TEST_UTIL.createTable(
552        TableName.valueOf(name.getMethodName()), new byte[][] { FAMILY });
553    try {
554      Put put = new Put(ROW);
555      put.addColumn(FAMILY, QUALIFIER, VALUE);
556      table.put(put);
557      put = new Put(ROW2);
558      put.addColumn(FAMILY, QUALIFIER, VALUE);
559      table.put(put);
560
561      Get get = new Get(ROW);
562      Get get2 = new Get(ROW2);
563      ArrayList<Get> getList = new ArrayList(2);
564      getList.add(get);
565      getList.add(get2);
566
567      boolean[] exists = table.existsAll(getList);
568      assertEquals(true, exists[0]);
569      assertEquals(true, exists[1]);
570
571      Result[] result = table.get(getList);
572      assertEquals(false, result[0].isEmpty());
573      assertTrue(Bytes.equals(VALUE, result[0].getValue(FAMILY, QUALIFIER)));
574      assertEquals(false, result[1].isEmpty());
575      assertTrue(Bytes.equals(VALUE, result[1].getValue(FAMILY, QUALIFIER)));
576    } finally {
577      table.close();
578    }
579  }
580
581  @Test
582  public void testHTableExistsMethodMultipleRegionsSingleGet() throws Exception {
583    Table table = TEST_UTIL.createTable(
584      TableName.valueOf(name.getMethodName()), new byte[][] { FAMILY },
585      1, new byte[] { 0x00 }, new byte[] { (byte) 0xff }, 255);
586    Put put = new Put(ROW);
587    put.addColumn(FAMILY, QUALIFIER, VALUE);
588
589    Get get = new Get(ROW);
590
591    boolean exist = table.exists(get);
592    assertFalse(exist);
593
594    table.put(put);
595
596    exist = table.exists(get);
597    assertTrue(exist);
598  }
599
600  @Test
601  public void testHTableExistsMethodMultipleRegionsMultipleGets() throws Exception {
602    Table table = TEST_UTIL.createTable(
603      TableName.valueOf(name.getMethodName()),
604      new byte[][] { FAMILY }, 1, new byte[] { 0x00 }, new byte[] { (byte) 0xff }, 255);
605    Put put = new Put(ROW);
606    put.addColumn(FAMILY, QUALIFIER, VALUE);
607    table.put (put);
608
609    List<Get> gets = new ArrayList<>();
610    gets.add(new Get(ANOTHERROW));
611    gets.add(new Get(Bytes.add(ROW, new byte[] { 0x00 })));
612    gets.add(new Get(ROW));
613    gets.add(new Get(Bytes.add(ANOTHERROW, new byte[] { 0x00 })));
614
615    LOG.info("Calling exists");
616    boolean[] results = table.existsAll(gets);
617    assertFalse(results[0]);
618    assertFalse(results[1]);
619    assertTrue(results[2]);
620    assertFalse(results[3]);
621
622    // Test with the first region.
623    put = new Put(new byte[] { 0x00 });
624    put.addColumn(FAMILY, QUALIFIER, VALUE);
625    table.put(put);
626
627    gets = new ArrayList<>();
628    gets.add(new Get(new byte[] { 0x00 }));
629    gets.add(new Get(new byte[] { 0x00, 0x00 }));
630    results = table.existsAll(gets);
631    assertTrue(results[0]);
632    assertFalse(results[1]);
633
634    // Test with the last region
635    put = new Put(new byte[] { (byte) 0xff, (byte) 0xff });
636    put.addColumn(FAMILY, QUALIFIER, VALUE);
637    table.put(put);
638
639    gets = new ArrayList<>();
640    gets.add(new Get(new byte[] { (byte) 0xff }));
641    gets.add(new Get(new byte[] { (byte) 0xff, (byte) 0xff }));
642    gets.add(new Get(new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff }));
643    results = table.existsAll(gets);
644    assertFalse(results[0]);
645    assertTrue(results[1]);
646    assertFalse(results[2]);
647  }
648
649  @Test
650  public void testGetEmptyRow() throws Exception {
651    //Create a table and put in 1 row
652    Admin admin = TEST_UTIL.getAdmin();
653    HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(Bytes.toBytes(name.getMethodName())));
654    desc.addFamily(new HColumnDescriptor(FAMILY));
655    admin.createTable(desc);
656    Table table = TEST_UTIL.getConnection().getTable(desc.getTableName());
657
658    Put put = new Put(ROW_BYTES);
659    put.addColumn(FAMILY, COL_QUAL, VAL_BYTES);
660    table.put(put);
661
662    //Try getting the row with an empty row key
663    Result res = null;
664    try {
665      res = table.get(new Get(new byte[0]));
666      fail();
667    } catch (IllegalArgumentException e) {
668      // Expected.
669    }
670    assertTrue(res == null);
671    res = table.get(new Get(Bytes.toBytes("r1-not-exist")));
672    assertTrue(res.isEmpty() == true);
673    res = table.get(new Get(ROW_BYTES));
674    assertTrue(Arrays.equals(res.getValue(FAMILY, COL_QUAL), VAL_BYTES));
675    table.close();
676  }
677
678  @Test
679  public void testConnectionDefaultUsesCodec() throws Exception {
680    ClusterConnection con = (ClusterConnection) TEST_UTIL.getConnection();
681    assertTrue(con.hasCellBlockSupport());
682  }
683
684  @Test
685  public void testPutWithPreBatchMutate() throws Exception {
686    final TableName tableName = TableName.valueOf(name.getMethodName());
687    testPreBatchMutate(tableName, () -> {
688      try {
689        Table t = TEST_UTIL.getConnection().getTable(tableName);
690        Put put = new Put(ROW);
691        put.addColumn(FAMILY, QUALIFIER, VALUE);
692        t.put(put);
693      } catch (IOException ex) {
694        throw new RuntimeException(ex);
695      }
696    });
697  }
698
699  @Test
700  public void testRowMutationsWithPreBatchMutate() throws Exception {
701    final TableName tableName = TableName.valueOf(name.getMethodName());
702    testPreBatchMutate(tableName, () -> {
703      try {
704        RowMutations rm = new RowMutations(ROW, 1);
705        Table t = TEST_UTIL.getConnection().getTable(tableName);
706        Put put = new Put(ROW);
707        put.addColumn(FAMILY, QUALIFIER, VALUE);
708        rm.add(put);
709        t.mutateRow(rm);
710      } catch (IOException ex) {
711        throw new RuntimeException(ex);
712      }
713    });
714  }
715
716  private void testPreBatchMutate(TableName tableName, Runnable rn)throws Exception {
717    HTableDescriptor desc = new HTableDescriptor(tableName);
718    desc.addCoprocessor(WaitingForScanObserver.class.getName());
719    desc.addFamily(new HColumnDescriptor(FAMILY));
720    TEST_UTIL.getAdmin().createTable(desc);
721    ExecutorService service = Executors.newFixedThreadPool(2);
722    service.execute(rn);
723    final List<Cell> cells = new ArrayList<>();
724    service.execute(() -> {
725      try {
726        // waiting for update.
727        TimeUnit.SECONDS.sleep(3);
728        Table t = TEST_UTIL.getConnection().getTable(tableName);
729        Scan scan = new Scan();
730        try (ResultScanner scanner = t.getScanner(scan)) {
731          for (Result r : scanner) {
732            cells.addAll(Arrays.asList(r.rawCells()));
733          }
734        }
735      } catch (IOException | InterruptedException ex) {
736        throw new RuntimeException(ex);
737      }
738    });
739    service.shutdown();
740    service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
741    assertEquals("The write is blocking by RegionObserver#postBatchMutate"
742      + ", so the data is invisible to reader", 0, cells.size());
743    TEST_UTIL.deleteTable(tableName);
744  }
745
746  @Test
747  public void testLockLeakWithDelta() throws Exception, Throwable {
748    final TableName tableName = TableName.valueOf(name.getMethodName());
749    HTableDescriptor desc = new HTableDescriptor(tableName);
750    desc.addCoprocessor(WaitingForMultiMutationsObserver.class.getName());
751    desc.setConfiguration("hbase.rowlock.wait.duration", String.valueOf(5000));
752    desc.addFamily(new HColumnDescriptor(FAMILY));
753    TEST_UTIL.getAdmin().createTable(desc);
754    // new a connection for lower retry number.
755    Configuration copy = new Configuration(TEST_UTIL.getConfiguration());
756    copy.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
757    try (Connection con = ConnectionFactory.createConnection(copy)) {
758      HRegion region = (HRegion) find(tableName);
759      region.setTimeoutForWriteLock(10);
760      ExecutorService putService = Executors.newSingleThreadExecutor();
761      putService.execute(() -> {
762        try (Table table = con.getTable(tableName)) {
763          Put put = new Put(ROW);
764          put.addColumn(FAMILY, QUALIFIER, VALUE);
765          // the put will be blocked by WaitingForMultiMutationsObserver.
766          table.put(put);
767        } catch (IOException ex) {
768          throw new RuntimeException(ex);
769        }
770      });
771      ExecutorService appendService = Executors.newSingleThreadExecutor();
772      appendService.execute(() -> {
773        Append append = new Append(ROW);
774        append.addColumn(FAMILY, QUALIFIER, VALUE);
775        try (Table table = con.getTable(tableName)) {
776          table.append(append);
777          fail("The APPEND should fail because the target lock is blocked by previous put");
778        } catch (Exception ex) {
779        }
780      });
781      appendService.shutdown();
782      appendService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
783      WaitingForMultiMutationsObserver observer = find(tableName, WaitingForMultiMutationsObserver.class);
784      observer.latch.countDown();
785      putService.shutdown();
786      putService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
787      try (Table table = con.getTable(tableName)) {
788        Result r = table.get(new Get(ROW));
789        assertFalse(r.isEmpty());
790        assertTrue(Bytes.equals(r.getValue(FAMILY, QUALIFIER), VALUE));
791      }
792    }
793    HRegion region = (HRegion) find(tableName);
794    int readLockCount = region.getReadLockCount();
795    LOG.info("readLockCount:" + readLockCount);
796    assertEquals(0, readLockCount);
797  }
798
799  @Test
800  public void testMultiRowMutations() throws Exception, Throwable {
801    final TableName tableName = TableName.valueOf(name.getMethodName());
802    HTableDescriptor desc = new HTableDescriptor(tableName);
803    desc.addCoprocessor(MultiRowMutationEndpoint.class.getName());
804    desc.addCoprocessor(WaitingForMultiMutationsObserver.class.getName());
805    desc.setConfiguration("hbase.rowlock.wait.duration", String.valueOf(5000));
806    desc.addFamily(new HColumnDescriptor(FAMILY));
807    TEST_UTIL.getAdmin().createTable(desc);
808    // new a connection for lower retry number.
809    Configuration copy = new Configuration(TEST_UTIL.getConfiguration());
810    copy.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
811    try (Connection con = ConnectionFactory.createConnection(copy)) {
812      byte[] row = Bytes.toBytes("ROW-0");
813      byte[] rowLocked= Bytes.toBytes("ROW-1");
814      byte[] value0 = Bytes.toBytes("VALUE-0");
815      byte[] value1 = Bytes.toBytes("VALUE-1");
816      byte[] value2 = Bytes.toBytes("VALUE-2");
817      assertNoLocks(tableName);
818      ExecutorService putService = Executors.newSingleThreadExecutor();
819      putService.execute(() -> {
820        try (Table table = con.getTable(tableName)) {
821          Put put0 = new Put(rowLocked);
822          put0.addColumn(FAMILY, QUALIFIER, value0);
823          // the put will be blocked by WaitingForMultiMutationsObserver.
824          table.put(put0);
825        } catch (IOException ex) {
826          throw new RuntimeException(ex);
827        }
828      });
829      ExecutorService cpService = Executors.newSingleThreadExecutor();
830      cpService.execute(() -> {
831        boolean threw;
832        Put put1 = new Put(row);
833        Put put2 = new Put(rowLocked);
834        put1.addColumn(FAMILY, QUALIFIER, value1);
835        put2.addColumn(FAMILY, QUALIFIER, value2);
836        try (Table table = con.getTable(tableName)) {
837          MultiRowMutationProtos.MutateRowsRequest request
838            = MultiRowMutationProtos.MutateRowsRequest.newBuilder()
839              .addMutationRequest(org.apache.hadoop.hbase.protobuf.ProtobufUtil.toMutation(
840                      org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType.PUT, put1))
841              .addMutationRequest(org.apache.hadoop.hbase.protobuf.ProtobufUtil.toMutation(
842                      org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType.PUT, put2))
843              .build();
844          table.coprocessorService(MultiRowMutationProtos.MultiRowMutationService.class,
845            ROW, ROW,
846            (MultiRowMutationProtos.MultiRowMutationService exe) -> {
847              ServerRpcController controller = new ServerRpcController();
848              CoprocessorRpcUtils.BlockingRpcCallback<MultiRowMutationProtos.MutateRowsResponse>
849                rpcCallback = new CoprocessorRpcUtils.BlockingRpcCallback<>();
850              exe.mutateRows(controller, request, rpcCallback);
851              return rpcCallback.get();
852            });
853          threw = false;
854        } catch (Throwable ex) {
855          threw = true;
856        }
857        if (!threw) {
858          // Can't call fail() earlier because the catch would eat it.
859          fail("This cp should fail because the target lock is blocked by previous put");
860        }
861      });
862      cpService.shutdown();
863      cpService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
864      WaitingForMultiMutationsObserver observer = find(tableName, WaitingForMultiMutationsObserver.class);
865      observer.latch.countDown();
866      putService.shutdown();
867      putService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
868      try (Table table = con.getTable(tableName)) {
869        Get g0 = new Get(row);
870        Get g1 = new Get(rowLocked);
871        Result r0 = table.get(g0);
872        Result r1 = table.get(g1);
873        assertTrue(r0.isEmpty());
874        assertFalse(r1.isEmpty());
875        assertTrue(Bytes.equals(r1.getValue(FAMILY, QUALIFIER), value0));
876      }
877      assertNoLocks(tableName);
878    }
879  }
880
881  /**
882   * A test case for issue HBASE-17482
883   * After combile seqid with mvcc readpoint, seqid/mvcc is acquired and stamped
884   * onto cells in the append thread, a countdown latch is used to ensure that happened
885   * before cells can be put into memstore. But the MVCCPreAssign patch(HBASE-16698)
886   * make the seqid/mvcc acquirement in handler thread and stamping in append thread
887   * No countdown latch to assure cells in memstore are stamped with seqid/mvcc.
888   * If cells without mvcc(A.K.A mvcc=0) are put into memstore, then a scanner
889   * with a smaller readpoint can see these data, which disobey the multi version
890   * concurrency control rules.
891   * This test case is to reproduce this scenario.
892   * @throws IOException
893   */
894  @Test
895  public void testMVCCUsingMVCCPreAssign() throws IOException {
896    final TableName tableName = TableName.valueOf(name.getMethodName());
897    HTableDescriptor htd = new HTableDescriptor(tableName);
898    HColumnDescriptor fam = new HColumnDescriptor(FAMILY);
899    htd.addFamily(fam);
900    Admin admin = TEST_UTIL.getAdmin();
901    admin.createTable(htd);
902    Table table = admin.getConnection().getTable(TableName.valueOf(name.getMethodName()));
903    //put two row first to init the scanner
904    Put put = new Put(Bytes.toBytes("0"));
905    put.addColumn(FAMILY, Bytes.toBytes( ""), Bytes.toBytes("0"));
906    table.put(put);
907    put = new Put(Bytes.toBytes("00"));
908    put.addColumn(FAMILY, Bytes.toBytes( ""), Bytes.toBytes("0"));
909    table.put(put);
910    Scan scan = new Scan();
911    scan.setTimeRange(0, Long.MAX_VALUE);
912    scan.setCaching(1);
913    ResultScanner scanner = table.getScanner(scan);
914    int rowNum = scanner.next() != null ? 1 : 0;
915    //the started scanner shouldn't see the rows put below
916    for(int i = 1; i < 1000; i++) {
917      put = new Put(Bytes.toBytes(String.valueOf(i)));
918      put.setDurability(Durability.ASYNC_WAL);
919      put.addColumn(FAMILY, Bytes.toBytes( ""), Bytes.toBytes(i));
920      table.put(put);
921    }
922    for(Result result : scanner) {
923      rowNum++;
924    }
925    //scanner should only see two rows
926    assertEquals(2, rowNum);
927    scanner = table.getScanner(scan);
928    rowNum = 0;
929    for(Result result : scanner) {
930      rowNum++;
931    }
932    // the new scanner should see all rows
933    assertEquals(1001, rowNum);
934
935
936  }
937
938  @Test
939  public void testPutThenGetWithMultipleThreads() throws Exception {
940    final TableName tableName = TableName.valueOf(name.getMethodName());
941    final int THREAD_NUM = 20;
942    final int ROUND_NUM = 10;
943    for (int round = 0; round < ROUND_NUM; round++) {
944      ArrayList<Thread> threads = new ArrayList<>(THREAD_NUM);
945      final AtomicInteger successCnt = new AtomicInteger(0);
946      Table ht = TEST_UTIL.createTable(tableName, FAMILY);
947      for (int i = 0; i < THREAD_NUM; i++) {
948        final int index = i;
949        Thread t = new Thread(new Runnable() {
950
951          @Override
952          public void run() {
953            final byte[] row = Bytes.toBytes("row-" + index);
954            final byte[] value = Bytes.toBytes("v" + index);
955            try {
956              Put put = new Put(row);
957              put.addColumn(FAMILY, QUALIFIER, value);
958              ht.put(put);
959              Get get = new Get(row);
960              Result result = ht.get(get);
961              byte[] returnedValue = result.getValue(FAMILY, QUALIFIER);
962              if (Bytes.equals(value, returnedValue)) {
963                successCnt.getAndIncrement();
964              } else {
965                LOG.error("Should be equal but not, original value: " + Bytes.toString(value)
966                    + ", returned value: "
967                    + (returnedValue == null ? "null" : Bytes.toString(returnedValue)));
968              }
969            } catch (Throwable e) {
970              // do nothing
971            }
972          }
973        });
974        threads.add(t);
975      }
976      for (Thread t : threads) {
977        t.start();
978      }
979      for (Thread t : threads) {
980        t.join();
981      }
982      assertEquals("Not equal in round " + round, THREAD_NUM, successCnt.get());
983      ht.close();
984      TEST_UTIL.deleteTable(tableName);
985    }
986  }
987
988  private static void assertNoLocks(final TableName tableName) throws IOException, InterruptedException {
989    HRegion region = (HRegion) find(tableName);
990    assertEquals(0, region.getLockedRows().size());
991  }
992  private static HRegion find(final TableName tableName)
993      throws IOException, InterruptedException {
994    HRegionServer rs = TEST_UTIL.getRSForFirstRegionInTable(tableName);
995    List<HRegion> regions = rs.getRegions(tableName);
996    assertEquals(1, regions.size());
997    return regions.get(0);
998  }
999
1000  private static <T extends RegionObserver> T find(final TableName tableName,
1001          Class<T> clz) throws IOException, InterruptedException {
1002    HRegion region = find(tableName);
1003    Coprocessor cp = region.getCoprocessorHost().findCoprocessor(clz.getName());
1004    assertTrue("The cp instance should be " + clz.getName()
1005            + ", current instance is " + cp.getClass().getName(), clz.isInstance(cp));
1006    return clz.cast(cp);
1007  }
1008
1009  public static class WaitingForMultiMutationsObserver
1010      implements RegionCoprocessor, RegionObserver {
1011    final CountDownLatch latch = new CountDownLatch(1);
1012
1013    @Override
1014    public Optional<RegionObserver> getRegionObserver() {
1015      return Optional.of(this);
1016    }
1017
1018    @Override
1019    public void postBatchMutate(final ObserverContext<RegionCoprocessorEnvironment> c,
1020            final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1021      try {
1022        latch.await();
1023      } catch (InterruptedException ex) {
1024        throw new IOException(ex);
1025      }
1026    }
1027  }
1028
1029  public static class WaitingForScanObserver implements RegionCoprocessor, RegionObserver {
1030    private final CountDownLatch latch = new CountDownLatch(1);
1031
1032    @Override
1033    public Optional<RegionObserver> getRegionObserver() {
1034      return Optional.of(this);
1035    }
1036
1037    @Override
1038    public void postBatchMutate(final ObserverContext<RegionCoprocessorEnvironment> c,
1039            final MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
1040      try {
1041        // waiting for scanner
1042        latch.await();
1043      } catch (InterruptedException ex) {
1044        throw new IOException(ex);
1045      }
1046    }
1047
1048    @Override
1049    public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> e,
1050            final Scan scan, final RegionScanner s) throws IOException {
1051      latch.countDown();
1052      return s;
1053    }
1054  }
1055}