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}