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.coprocessor; 019 020import static org.junit.jupiter.api.Assertions.assertArrayEquals; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertFalse; 023import static org.junit.jupiter.api.Assertions.assertNotNull; 024import static org.junit.jupiter.api.Assertions.assertTrue; 025 026import java.io.IOException; 027import java.lang.reflect.Method; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Optional; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.fs.FileSystem; 034import org.apache.hadoop.fs.Path; 035import org.apache.hadoop.hbase.CellUtil; 036import org.apache.hadoop.hbase.CompareOperator; 037import org.apache.hadoop.hbase.Coprocessor; 038import org.apache.hadoop.hbase.ExtendedCell; 039import org.apache.hadoop.hbase.HBaseTestingUtil; 040import org.apache.hadoop.hbase.KeyValue; 041import org.apache.hadoop.hbase.ServerName; 042import org.apache.hadoop.hbase.SingleProcessHBaseCluster; 043import org.apache.hadoop.hbase.TableName; 044import org.apache.hadoop.hbase.client.Admin; 045import org.apache.hadoop.hbase.client.Append; 046import org.apache.hadoop.hbase.client.CheckAndMutate; 047import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 048import org.apache.hadoop.hbase.client.Delete; 049import org.apache.hadoop.hbase.client.Durability; 050import org.apache.hadoop.hbase.client.Get; 051import org.apache.hadoop.hbase.client.Increment; 052import org.apache.hadoop.hbase.client.Mutation; 053import org.apache.hadoop.hbase.client.Put; 054import org.apache.hadoop.hbase.client.RegionInfo; 055import org.apache.hadoop.hbase.client.RegionLocator; 056import org.apache.hadoop.hbase.client.Result; 057import org.apache.hadoop.hbase.client.ResultScanner; 058import org.apache.hadoop.hbase.client.RowMutations; 059import org.apache.hadoop.hbase.client.Scan; 060import org.apache.hadoop.hbase.client.Table; 061import org.apache.hadoop.hbase.client.TableDescriptor; 062import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 063import org.apache.hadoop.hbase.filter.FilterAllFilter; 064import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; 065import org.apache.hadoop.hbase.io.hfile.CacheConfig; 066import org.apache.hadoop.hbase.io.hfile.HFile; 067import org.apache.hadoop.hbase.io.hfile.HFileContext; 068import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder; 069import org.apache.hadoop.hbase.regionserver.FlushLifeCycleTracker; 070import org.apache.hadoop.hbase.regionserver.HRegion; 071import org.apache.hadoop.hbase.regionserver.InternalScanner; 072import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost; 073import org.apache.hadoop.hbase.regionserver.ScanType; 074import org.apache.hadoop.hbase.regionserver.ScannerContext; 075import org.apache.hadoop.hbase.regionserver.Store; 076import org.apache.hadoop.hbase.regionserver.StoreFile; 077import org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker; 078import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest; 079import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener; 080import org.apache.hadoop.hbase.testclassification.CoprocessorTests; 081import org.apache.hadoop.hbase.testclassification.LargeTests; 082import org.apache.hadoop.hbase.tool.BulkLoadHFiles; 083import org.apache.hadoop.hbase.util.Bytes; 084import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 085import org.apache.hadoop.hbase.util.JVMClusterUtil; 086import org.apache.hadoop.hbase.util.Threads; 087import org.apache.hadoop.hbase.wal.WALEdit; 088import org.apache.hadoop.hbase.wal.WALKey; 089import org.apache.hadoop.hbase.wal.WALKeyImpl; 090import org.junit.jupiter.api.AfterAll; 091import org.junit.jupiter.api.BeforeAll; 092import org.junit.jupiter.api.BeforeEach; 093import org.junit.jupiter.api.Tag; 094import org.junit.jupiter.api.Test; 095import org.junit.jupiter.api.TestInfo; 096import org.mockito.Mockito; 097import org.slf4j.Logger; 098import org.slf4j.LoggerFactory; 099 100import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 101 102@Tag(CoprocessorTests.TAG) 103@Tag(LargeTests.TAG) 104public class TestRegionObserverInterface { 105 106 private static final Logger LOG = LoggerFactory.getLogger(TestRegionObserverInterface.class); 107 108 public static final TableName TEST_TABLE = TableName.valueOf("TestTable"); 109 public static final byte[] FAMILY = Bytes.toBytes("f"); 110 public final static byte[] A = Bytes.toBytes("a"); 111 public final static byte[] B = Bytes.toBytes("b"); 112 public final static byte[] C = Bytes.toBytes("c"); 113 public final static byte[] ROW = Bytes.toBytes("testrow"); 114 115 private static HBaseTestingUtil util = new HBaseTestingUtil(); 116 private static SingleProcessHBaseCluster cluster = null; 117 118 private String currentTestName; 119 120 @BeforeEach 121 public void setUp(TestInfo testInfo) { 122 currentTestName = testInfo.getTestMethod().get().getName(); 123 } 124 125 @BeforeAll 126 public static void setupBeforeClass() throws Exception { 127 // set configure to indicate which cp should be loaded 128 Configuration conf = util.getConfiguration(); 129 conf.setBoolean("hbase.master.distributed.log.replay", true); 130 conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, 131 SimpleRegionObserver.class.getName()); 132 133 util.startMiniCluster(); 134 cluster = util.getMiniHBaseCluster(); 135 } 136 137 @AfterAll 138 public static void tearDownAfterClass() throws Exception { 139 util.shutdownMiniCluster(); 140 } 141 142 @Test 143 public void testRegionObserver() throws IOException { 144 final TableName tableName = 145 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 146 // recreate table every time in order to reset the status of the 147 // coprocessor. 148 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 149 try { 150 verifyMethodResult(SimpleRegionObserver.class, 151 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadDelete", 152 "hadPostStartRegionOperation", "hadPostCloseRegionOperation", 153 "hadPostBatchMutateIndispensably" }, 154 tableName, new Boolean[] { false, false, false, false, false, false, false, false }); 155 156 Put put = new Put(ROW); 157 put.addColumn(A, A, A); 158 put.addColumn(B, B, B); 159 put.addColumn(C, C, C); 160 table.put(put); 161 162 verifyMethodResult(SimpleRegionObserver.class, 163 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadPreBatchMutate", 164 "hadPostBatchMutate", "hadDelete", "hadPostStartRegionOperation", 165 "hadPostCloseRegionOperation", "hadPostBatchMutateIndispensably" }, 166 TEST_TABLE, 167 new Boolean[] { false, false, true, true, true, true, false, true, true, true }); 168 169 verifyMethodResult(SimpleRegionObserver.class, 170 new String[] { "getCtPreOpen", "getCtPostOpen", "getCtPreClose", "getCtPostClose" }, 171 tableName, new Integer[] { 1, 1, 0, 0 }); 172 173 Get get = new Get(ROW); 174 get.addColumn(A, A); 175 get.addColumn(B, B); 176 get.addColumn(C, C); 177 table.get(get); 178 179 verifyMethodResult(SimpleRegionObserver.class, 180 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadDelete", 181 "hadPrePreparedDeleteTS" }, 182 tableName, new Boolean[] { true, true, true, true, false, false }); 183 184 Delete delete = new Delete(ROW); 185 delete.addColumn(A, A); 186 delete.addColumn(B, B); 187 delete.addColumn(C, C); 188 table.delete(delete); 189 190 verifyMethodResult(SimpleRegionObserver.class, 191 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadPreBatchMutate", 192 "hadPostBatchMutate", "hadDelete", "hadPrePreparedDeleteTS" }, 193 tableName, new Boolean[] { true, true, true, true, true, true, true, true }); 194 } finally { 195 util.deleteTable(tableName); 196 table.close(); 197 } 198 verifyMethodResult(SimpleRegionObserver.class, 199 new String[] { "getCtPreOpen", "getCtPostOpen", "getCtPreClose", "getCtPostClose" }, 200 tableName, new Integer[] { 1, 1, 1, 1 }); 201 } 202 203 @Test 204 public void testRowMutation() throws IOException { 205 final TableName tableName = 206 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 207 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 208 try { 209 verifyMethodResult(SimpleRegionObserver.class, 210 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadDeleted" }, 211 tableName, new Boolean[] { false, false, false, false, false }); 212 Put put = new Put(ROW); 213 put.addColumn(A, A, A); 214 put.addColumn(B, B, B); 215 put.addColumn(C, C, C); 216 217 Delete delete = new Delete(ROW); 218 delete.addColumn(A, A); 219 delete.addColumn(B, B); 220 delete.addColumn(C, C); 221 222 RowMutations arm = new RowMutations(ROW); 223 arm.add(put); 224 arm.add(delete); 225 table.mutateRow(arm); 226 227 verifyMethodResult(SimpleRegionObserver.class, 228 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadDeleted" }, 229 tableName, new Boolean[] { false, false, true, true, true }); 230 } finally { 231 util.deleteTable(tableName); 232 table.close(); 233 } 234 } 235 236 @Test 237 public void testIncrementHook() throws IOException { 238 final TableName tableName = 239 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 240 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 241 try { 242 Increment inc = new Increment(Bytes.toBytes(0)); 243 inc.addColumn(A, A, 1); 244 245 verifyMethodResult(SimpleRegionObserver.class, 246 new String[] { "hadPreIncrement", "hadPostIncrement", "hadPreIncrementAfterRowLock", 247 "hadPreBatchMutate", "hadPostBatchMutate", "hadPostBatchMutateIndispensably" }, 248 tableName, new Boolean[] { false, false, false, false, false, false }); 249 250 table.increment(inc); 251 252 verifyMethodResult(SimpleRegionObserver.class, 253 new String[] { "hadPreIncrement", "hadPostIncrement", "hadPreIncrementAfterRowLock", 254 "hadPreBatchMutate", "hadPostBatchMutate", "hadPostBatchMutateIndispensably" }, 255 tableName, new Boolean[] { true, true, true, true, true, true }); 256 } finally { 257 util.deleteTable(tableName); 258 table.close(); 259 } 260 } 261 262 @Test 263 public void testCheckAndPutHooks() throws IOException { 264 final TableName tableName = 265 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 266 try (Table table = util.createTable(tableName, new byte[][] { A, B, C })) { 267 Put p = new Put(Bytes.toBytes(0)); 268 p.addColumn(A, A, A); 269 table.put(p); 270 p = new Put(Bytes.toBytes(0)); 271 p.addColumn(A, A, A); 272 verifyMethodResult(SimpleRegionObserver.class, 273 new String[] { "getPreCheckAndPut", "getPreCheckAndPutAfterRowLock", "getPostCheckAndPut", 274 "getPreCheckAndPutWithFilter", "getPreCheckAndPutWithFilterAfterRowLock", 275 "getPostCheckAndPutWithFilter", "getPreCheckAndMutate", 276 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 277 tableName, new Integer[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }); 278 279 table.checkAndMutate(Bytes.toBytes(0), A).qualifier(A).ifEquals(A).thenPut(p); 280 verifyMethodResult(SimpleRegionObserver.class, 281 new String[] { "getPreCheckAndPut", "getPreCheckAndPutAfterRowLock", "getPostCheckAndPut", 282 "getPreCheckAndPutWithFilter", "getPreCheckAndPutWithFilterAfterRowLock", 283 "getPostCheckAndPutWithFilter", "getPreCheckAndMutate", 284 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 285 tableName, new Integer[] { 1, 1, 1, 0, 0, 0, 1, 1, 1 }); 286 287 table.checkAndMutate(Bytes.toBytes(0), 288 new SingleColumnValueFilter(A, A, CompareOperator.EQUAL, A)).thenPut(p); 289 verifyMethodResult(SimpleRegionObserver.class, 290 new String[] { "getPreCheckAndPut", "getPreCheckAndPutAfterRowLock", "getPostCheckAndPut", 291 "getPreCheckAndPutWithFilter", "getPreCheckAndPutWithFilterAfterRowLock", 292 "getPostCheckAndPutWithFilter", "getPreCheckAndMutate", 293 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 294 tableName, new Integer[] { 1, 1, 1, 1, 1, 1, 2, 2, 2 }); 295 } finally { 296 util.deleteTable(tableName); 297 } 298 } 299 300 @Test 301 public void testCheckAndDeleteHooks() throws IOException { 302 final TableName tableName = 303 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 304 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 305 try { 306 Put p = new Put(Bytes.toBytes(0)); 307 p.addColumn(A, A, A); 308 table.put(p); 309 Delete d = new Delete(Bytes.toBytes(0)); 310 table.delete(d); 311 verifyMethodResult(SimpleRegionObserver.class, 312 new String[] { "getPreCheckAndDelete", "getPreCheckAndDeleteAfterRowLock", 313 "getPostCheckAndDelete", "getPreCheckAndDeleteWithFilter", 314 "getPreCheckAndDeleteWithFilterAfterRowLock", "getPostCheckAndDeleteWithFilter", 315 "getPreCheckAndMutate", "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 316 tableName, new Integer[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }); 317 318 table.checkAndMutate(Bytes.toBytes(0), A).qualifier(A).ifEquals(A).thenDelete(d); 319 verifyMethodResult(SimpleRegionObserver.class, 320 new String[] { "getPreCheckAndDelete", "getPreCheckAndDeleteAfterRowLock", 321 "getPostCheckAndDelete", "getPreCheckAndDeleteWithFilter", 322 "getPreCheckAndDeleteWithFilterAfterRowLock", "getPostCheckAndDeleteWithFilter", 323 "getPreCheckAndMutate", "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 324 tableName, new Integer[] { 1, 1, 1, 0, 0, 0, 1, 1, 1 }); 325 326 table.checkAndMutate(Bytes.toBytes(0), 327 new SingleColumnValueFilter(A, A, CompareOperator.EQUAL, A)).thenDelete(d); 328 verifyMethodResult(SimpleRegionObserver.class, 329 new String[] { "getPreCheckAndDelete", "getPreCheckAndDeleteAfterRowLock", 330 "getPostCheckAndDelete", "getPreCheckAndDeleteWithFilter", 331 "getPreCheckAndDeleteWithFilterAfterRowLock", "getPostCheckAndDeleteWithFilter", 332 "getPreCheckAndMutate", "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 333 tableName, new Integer[] { 1, 1, 1, 1, 1, 1, 2, 2, 2 }); 334 } finally { 335 util.deleteTable(tableName); 336 table.close(); 337 } 338 } 339 340 @Test 341 public void testCheckAndIncrementHooks() throws Exception { 342 final TableName tableName = 343 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 344 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 345 try { 346 byte[] row = Bytes.toBytes(0); 347 348 verifyMethodResult( 349 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 350 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 351 tableName, new Integer[] { 0, 0, 0 }); 352 353 table.checkAndMutate(CheckAndMutate.newBuilder(row).ifNotExists(A, A) 354 .build(new Increment(row).addColumn(A, A, 1))); 355 verifyMethodResult( 356 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 357 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 358 tableName, new Integer[] { 1, 1, 1 }); 359 360 table.checkAndMutate(CheckAndMutate.newBuilder(row).ifEquals(A, A, Bytes.toBytes(1L)) 361 .build(new Increment(row).addColumn(A, A, 1))); 362 verifyMethodResult( 363 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 364 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 365 tableName, new Integer[] { 2, 2, 2 }); 366 } finally { 367 util.deleteTable(tableName); 368 table.close(); 369 } 370 } 371 372 @Test 373 public void testCheckAndAppendHooks() throws Exception { 374 final TableName tableName = 375 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 376 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 377 try { 378 byte[] row = Bytes.toBytes(0); 379 380 verifyMethodResult( 381 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 382 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 383 tableName, new Integer[] { 0, 0, 0 }); 384 385 table.checkAndMutate( 386 CheckAndMutate.newBuilder(row).ifNotExists(A, A).build(new Append(row).addColumn(A, A, A))); 387 verifyMethodResult( 388 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 389 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 390 tableName, new Integer[] { 1, 1, 1 }); 391 392 table.checkAndMutate( 393 CheckAndMutate.newBuilder(row).ifEquals(A, A, A).build(new Append(row).addColumn(A, A, A))); 394 verifyMethodResult( 395 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 396 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 397 tableName, new Integer[] { 2, 2, 2 }); 398 } finally { 399 util.deleteTable(tableName); 400 table.close(); 401 } 402 } 403 404 @Test 405 public void testCheckAndRowMutationsHooks() throws Exception { 406 final TableName tableName = 407 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 408 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 409 try { 410 byte[] row = Bytes.toBytes(0); 411 412 Put p = new Put(row).addColumn(A, A, A); 413 table.put(p); 414 verifyMethodResult( 415 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 416 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 417 tableName, new Integer[] { 0, 0, 0 }); 418 419 table 420 .checkAndMutate(CheckAndMutate.newBuilder(row).ifEquals(A, A, A).build(new RowMutations(row) 421 .add((Mutation) new Put(row).addColumn(B, B, B)).add((Mutation) new Delete(row)))); 422 verifyMethodResult( 423 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 424 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 425 tableName, new Integer[] { 1, 1, 1 }); 426 427 Object[] result = new Object[2]; 428 table.batch( 429 Arrays.asList(p, 430 CheckAndMutate.newBuilder(row).ifEquals(A, A, A).build(new RowMutations(row) 431 .add((Mutation) new Put(row).addColumn(B, B, B)).add((Mutation) new Delete(row)))), 432 result); 433 verifyMethodResult( 434 SimpleRegionObserver.class, new String[] { "getPreCheckAndMutate", 435 "getPreCheckAndMutateAfterRowLock", "getPostCheckAndMutate" }, 436 tableName, new Integer[] { 2, 2, 2 }); 437 } finally { 438 util.deleteTable(tableName); 439 table.close(); 440 } 441 } 442 443 @Test 444 public void testAppendHook() throws IOException { 445 final TableName tableName = 446 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 447 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 448 try { 449 Append app = new Append(Bytes.toBytes(0)); 450 app.addColumn(A, A, A); 451 452 verifyMethodResult(SimpleRegionObserver.class, 453 new String[] { "hadPreAppend", "hadPostAppend", "hadPreAppendAfterRowLock", 454 "hadPreBatchMutate", "hadPostBatchMutate", "hadPostBatchMutateIndispensably" }, 455 tableName, new Boolean[] { false, false, false, false, false, false }); 456 457 table.append(app); 458 459 verifyMethodResult(SimpleRegionObserver.class, 460 new String[] { "hadPreAppend", "hadPostAppend", "hadPreAppendAfterRowLock", 461 "hadPreBatchMutate", "hadPostBatchMutate", "hadPostBatchMutateIndispensably" }, 462 tableName, new Boolean[] { true, true, true, true, true, true }); 463 } finally { 464 util.deleteTable(tableName); 465 table.close(); 466 } 467 } 468 469 @Test 470 // HBase-3583 471 public void testHBase3583() throws IOException { 472 final TableName tableName = TableName.valueOf(currentTestName); 473 util.createTable(tableName, new byte[][] { A, B, C }); 474 util.waitUntilAllRegionsAssigned(tableName); 475 476 verifyMethodResult(SimpleRegionObserver.class, 477 new String[] { "hadPreGet", "hadPostGet", "wasScannerNextCalled", "wasScannerCloseCalled" }, 478 tableName, new Boolean[] { false, false, false, false }); 479 480 Table table = util.getConnection().getTable(tableName); 481 Put put = new Put(ROW); 482 put.addColumn(A, A, A); 483 table.put(put); 484 485 Get get = new Get(ROW); 486 get.addColumn(A, A); 487 table.get(get); 488 489 // verify that scannerNext and scannerClose upcalls won't be invoked 490 // when we perform get(). 491 verifyMethodResult(SimpleRegionObserver.class, 492 new String[] { "hadPreGet", "hadPostGet", "wasScannerNextCalled", "wasScannerCloseCalled" }, 493 tableName, new Boolean[] { true, true, false, false }); 494 495 Scan s = new Scan(); 496 ResultScanner scanner = table.getScanner(s); 497 try { 498 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) { 499 } 500 } finally { 501 scanner.close(); 502 } 503 504 // now scanner hooks should be invoked. 505 verifyMethodResult(SimpleRegionObserver.class, 506 new String[] { "wasScannerNextCalled", "wasScannerCloseCalled" }, tableName, 507 new Boolean[] { true, true }); 508 util.deleteTable(tableName); 509 table.close(); 510 } 511 512 @Test 513 public void testHBASE14489() throws IOException { 514 final TableName tableName = TableName.valueOf(currentTestName); 515 Table table = util.createTable(tableName, new byte[][] { A }); 516 Put put = new Put(ROW); 517 put.addColumn(A, A, A); 518 table.put(put); 519 520 Scan s = new Scan(); 521 s.setFilter(new FilterAllFilter()); 522 ResultScanner scanner = table.getScanner(s); 523 try { 524 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) { 525 } 526 } finally { 527 scanner.close(); 528 } 529 verifyMethodResult(SimpleRegionObserver.class, new String[] { "wasScannerFilterRowCalled" }, 530 tableName, new Boolean[] { true }); 531 util.deleteTable(tableName); 532 table.close(); 533 534 } 535 536 @Test 537 // HBase-3758 538 public void testHBase3758() throws IOException { 539 final TableName tableName = TableName.valueOf(currentTestName); 540 util.createTable(tableName, new byte[][] { A, B, C }); 541 542 verifyMethodResult(SimpleRegionObserver.class, 543 new String[] { "hadDeleted", "wasScannerOpenCalled" }, tableName, 544 new Boolean[] { false, false }); 545 546 Table table = util.getConnection().getTable(tableName); 547 Put put = new Put(ROW); 548 put.addColumn(A, A, A); 549 table.put(put); 550 551 Delete delete = new Delete(ROW); 552 table.delete(delete); 553 554 verifyMethodResult(SimpleRegionObserver.class, 555 new String[] { "hadDeleted", "wasScannerOpenCalled" }, tableName, 556 new Boolean[] { true, false }); 557 558 Scan s = new Scan(); 559 ResultScanner scanner = table.getScanner(s); 560 try { 561 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) { 562 } 563 } finally { 564 scanner.close(); 565 } 566 567 // now scanner hooks should be invoked. 568 verifyMethodResult(SimpleRegionObserver.class, new String[] { "wasScannerOpenCalled" }, 569 tableName, new Boolean[] { true }); 570 util.deleteTable(tableName); 571 table.close(); 572 } 573 574 /* Overrides compaction to only output rows with keys that are even numbers */ 575 public static class EvenOnlyCompactor implements RegionCoprocessor, RegionObserver { 576 long lastCompaction; 577 long lastFlush; 578 579 @Override 580 public Optional<RegionObserver> getRegionObserver() { 581 return Optional.of(this); 582 } 583 584 @Override 585 public InternalScanner preCompact(ObserverContext<? extends RegionCoprocessorEnvironment> e, 586 Store store, InternalScanner scanner, ScanType scanType, CompactionLifeCycleTracker tracker, 587 CompactionRequest request) { 588 return new InternalScanner() { 589 590 @Override 591 public boolean next(List<? super ExtendedCell> results, ScannerContext scannerContext) 592 throws IOException { 593 List<ExtendedCell> internalResults = new ArrayList<>(); 594 boolean hasMore; 595 do { 596 hasMore = scanner.next(internalResults, scannerContext); 597 if (!internalResults.isEmpty()) { 598 long row = Bytes.toLong(CellUtil.cloneValue(internalResults.get(0))); 599 if (row % 2 == 0) { 600 // return this row 601 break; 602 } 603 // clear and continue 604 internalResults.clear(); 605 } 606 } while (hasMore); 607 608 if (!internalResults.isEmpty()) { 609 results.addAll(internalResults); 610 } 611 return hasMore; 612 } 613 614 @Override 615 public void close() throws IOException { 616 scanner.close(); 617 } 618 }; 619 } 620 621 @Override 622 public void postCompact(ObserverContext<? extends RegionCoprocessorEnvironment> e, Store store, 623 StoreFile resultFile, CompactionLifeCycleTracker tracker, CompactionRequest request) { 624 lastCompaction = EnvironmentEdgeManager.currentTime(); 625 } 626 627 @Override 628 public void postFlush(ObserverContext<? extends RegionCoprocessorEnvironment> e, 629 FlushLifeCycleTracker tracker) { 630 lastFlush = EnvironmentEdgeManager.currentTime(); 631 } 632 } 633 634 /** 635 * Tests overriding compaction handling via coprocessor hooks 636 */ 637 @Test 638 public void testCompactionOverride() throws Exception { 639 final TableName compactTable = TableName.valueOf(currentTestName); 640 Admin admin = util.getAdmin(); 641 if (admin.tableExists(compactTable)) { 642 admin.disableTable(compactTable); 643 admin.deleteTable(compactTable); 644 } 645 646 TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(compactTable) 647 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(A)) 648 .setCoprocessor(EvenOnlyCompactor.class.getName()).build(); 649 admin.createTable(tableDescriptor); 650 651 Table table = util.getConnection().getTable(compactTable); 652 for (long i = 1; i <= 10; i++) { 653 byte[] iBytes = Bytes.toBytes(i); 654 Put put = new Put(iBytes); 655 put.setDurability(Durability.SKIP_WAL); 656 put.addColumn(A, A, iBytes); 657 table.put(put); 658 } 659 660 HRegion firstRegion = cluster.getRegions(compactTable).get(0); 661 Coprocessor cp = firstRegion.getCoprocessorHost().findCoprocessor(EvenOnlyCompactor.class); 662 assertNotNull(cp, "EvenOnlyCompactor coprocessor should be loaded"); 663 EvenOnlyCompactor compactor = (EvenOnlyCompactor) cp; 664 665 // force a compaction 666 long ts = EnvironmentEdgeManager.currentTime(); 667 admin.flush(compactTable); 668 // wait for flush 669 for (int i = 0; i < 10; i++) { 670 if (compactor.lastFlush >= ts) { 671 break; 672 } 673 Thread.sleep(1000); 674 } 675 assertTrue(compactor.lastFlush >= ts, "Flush didn't complete"); 676 LOG.debug("Flush complete"); 677 678 ts = compactor.lastFlush; 679 admin.majorCompact(compactTable); 680 // wait for compaction 681 for (int i = 0; i < 30; i++) { 682 if (compactor.lastCompaction >= ts) { 683 break; 684 } 685 Thread.sleep(1000); 686 } 687 LOG.debug("Last compaction was at " + compactor.lastCompaction); 688 assertTrue(compactor.lastCompaction >= ts, "Compaction didn't complete"); 689 690 // only even rows should remain 691 ResultScanner scanner = table.getScanner(new Scan()); 692 try { 693 for (long i = 2; i <= 10; i += 2) { 694 Result r = scanner.next(); 695 assertNotNull(r); 696 assertFalse(r.isEmpty()); 697 byte[] iBytes = Bytes.toBytes(i); 698 assertArrayEquals(r.getRow(), iBytes, "Row should be " + i); 699 assertArrayEquals(r.getValue(A, A), iBytes, "Value should be " + i); 700 } 701 } finally { 702 scanner.close(); 703 } 704 table.close(); 705 } 706 707 @Test 708 public void bulkLoadHFileTest() throws Exception { 709 final String testName = TestRegionObserverInterface.class.getName() + "." + currentTestName; 710 final TableName tableName = 711 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 712 Configuration conf = util.getConfiguration(); 713 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 714 try (RegionLocator locator = util.getConnection().getRegionLocator(tableName)) { 715 verifyMethodResult(SimpleRegionObserver.class, 716 new String[] { "hadPreBulkLoadHFile", "hadPostBulkLoadHFile" }, tableName, 717 new Boolean[] { false, false }); 718 719 FileSystem fs = util.getTestFileSystem(); 720 final Path dir = util.getDataTestDirOnTestFS(testName).makeQualified(fs); 721 Path familyDir = new Path(dir, Bytes.toString(A)); 722 723 createHFile(util.getConfiguration(), fs, new Path(familyDir, Bytes.toString(A)), A, A); 724 725 // Bulk load 726 BulkLoadHFiles.create(conf).bulkLoad(tableName, dir); 727 728 verifyMethodResult(SimpleRegionObserver.class, 729 new String[] { "hadPreBulkLoadHFile", "hadPostBulkLoadHFile" }, tableName, 730 new Boolean[] { true, true }); 731 } finally { 732 util.deleteTable(tableName); 733 table.close(); 734 } 735 } 736 737 @Test 738 public void testRecovery() throws Exception { 739 LOG.info(TestRegionObserverInterface.class.getName() + "." + currentTestName); 740 final TableName tableName = 741 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 742 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 743 try (RegionLocator locator = util.getConnection().getRegionLocator(tableName)) { 744 745 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer(); 746 ServerName sn2 = rs1.getRegionServer().getServerName(); 747 String regEN = locator.getAllRegionLocations().get(0).getRegion().getEncodedName(); 748 749 util.getAdmin().move(Bytes.toBytes(regEN), sn2); 750 while (!sn2.equals(locator.getAllRegionLocations().get(0).getServerName())) { 751 Thread.sleep(100); 752 } 753 754 Put put = new Put(ROW); 755 put.addColumn(A, A, A); 756 put.addColumn(B, B, B); 757 put.addColumn(C, C, C); 758 table.put(put); 759 760 // put two times 761 table.put(put); 762 763 verifyMethodResult(SimpleRegionObserver.class, 764 new String[] { "hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut", "hadPreBatchMutate", 765 "hadPostBatchMutate", "hadDelete" }, 766 tableName, new Boolean[] { false, false, true, true, true, true, false }); 767 768 verifyMethodResult(SimpleRegionObserver.class, 769 new String[] { "getCtPreReplayWALs", "getCtPostReplayWALs", "getCtPreWALRestore", 770 "getCtPostWALRestore", "getCtPrePut", "getCtPostPut" }, 771 tableName, new Integer[] { 0, 0, 0, 0, 2, 2 }); 772 773 cluster.killRegionServer(rs1.getRegionServer().getServerName()); 774 Threads.sleep(1000); // Let the kill soak in. 775 util.waitUntilAllRegionsAssigned(tableName); 776 LOG.info("All regions assigned"); 777 778 verifyMethodResult(SimpleRegionObserver.class, 779 new String[] { "getCtPreReplayWALs", "getCtPostReplayWALs", "getCtPreWALRestore", 780 "getCtPostWALRestore", "getCtPrePut", "getCtPostPut" }, 781 tableName, new Integer[] { 1, 1, 2, 2, 0, 0 }); 782 } finally { 783 util.deleteTable(tableName); 784 table.close(); 785 } 786 } 787 788 @Test 789 public void testPreWALRestoreSkip() throws Exception { 790 LOG.info(TestRegionObserverInterface.class.getName() + "." + currentTestName); 791 TableName tableName = TableName.valueOf(SimpleRegionObserver.TABLE_SKIPPED); 792 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 793 794 try (RegionLocator locator = util.getConnection().getRegionLocator(tableName)) { 795 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer(); 796 ServerName sn2 = rs1.getRegionServer().getServerName(); 797 String regEN = locator.getAllRegionLocations().get(0).getRegion().getEncodedName(); 798 799 util.getAdmin().move(Bytes.toBytes(regEN), sn2); 800 while (!sn2.equals(locator.getAllRegionLocations().get(0).getServerName())) { 801 Thread.sleep(100); 802 } 803 804 Put put = new Put(ROW); 805 put.addColumn(A, A, A); 806 put.addColumn(B, B, B); 807 put.addColumn(C, C, C); 808 table.put(put); 809 810 cluster.killRegionServer(rs1.getRegionServer().getServerName()); 811 Threads.sleep(20000); // just to be sure that the kill has fully started. 812 util.waitUntilAllRegionsAssigned(tableName); 813 } 814 815 verifyMethodResult(SimpleRegionObserver.class, 816 new String[] { "getCtPreWALRestore", "getCtPostWALRestore", }, tableName, 817 new Integer[] { 0, 0 }); 818 819 util.deleteTable(tableName); 820 table.close(); 821 } 822 823 // called from testPreWALAppendIsWrittenToWAL 824 private void testPreWALAppendHook(Table table, TableName tableName) throws IOException { 825 int expectedCalls = 0; 826 String[] methodArray = new String[1]; 827 methodArray[0] = "getCtPreWALAppend"; 828 Object[] resultArray = new Object[1]; 829 830 Put p = new Put(ROW); 831 p.addColumn(A, A, A); 832 table.put(p); 833 resultArray[0] = ++expectedCalls; 834 verifyMethodResult(SimpleRegionObserver.class, methodArray, tableName, resultArray); 835 836 Append a = new Append(ROW); 837 a.addColumn(B, B, B); 838 table.append(a); 839 resultArray[0] = ++expectedCalls; 840 verifyMethodResult(SimpleRegionObserver.class, methodArray, tableName, resultArray); 841 842 Increment i = new Increment(ROW); 843 i.addColumn(C, C, 1); 844 table.increment(i); 845 resultArray[0] = ++expectedCalls; 846 verifyMethodResult(SimpleRegionObserver.class, methodArray, tableName, resultArray); 847 848 Delete d = new Delete(ROW); 849 table.delete(d); 850 resultArray[0] = ++expectedCalls; 851 verifyMethodResult(SimpleRegionObserver.class, methodArray, tableName, resultArray); 852 } 853 854 @Test 855 public void testPreWALAppend() throws Exception { 856 SimpleRegionObserver sro = new SimpleRegionObserver(); 857 ObserverContext ctx = Mockito.mock(ObserverContext.class); 858 WALKey key = 859 new WALKeyImpl(Bytes.toBytes("region"), TEST_TABLE, EnvironmentEdgeManager.currentTime()); 860 WALEdit edit = new WALEdit(); 861 sro.preWALAppend(ctx, key, edit); 862 assertEquals(1, key.getExtendedAttributes().size()); 863 assertArrayEquals(SimpleRegionObserver.WAL_EXTENDED_ATTRIBUTE_BYTES, 864 key.getExtendedAttribute(Integer.toString(sro.getCtPreWALAppend()))); 865 } 866 867 @Test 868 public void testPreWALAppendIsWrittenToWAL() throws Exception { 869 final TableName tableName = 870 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 871 Table table = util.createTable(tableName, new byte[][] { A, B, C }); 872 873 PreWALAppendWALActionsListener listener = new PreWALAppendWALActionsListener(); 874 List<HRegion> regions = util.getHBaseCluster().getRegions(tableName); 875 // should be only one region 876 HRegion region = regions.get(0); 877 region.getWAL().registerWALActionsListener(listener); 878 testPreWALAppendHook(table, tableName); 879 boolean[] expectedResults = { true, true, true, true }; 880 assertArrayEquals(expectedResults, listener.getWalKeysCorrectArray()); 881 882 } 883 884 @Test 885 public void testPreWALAppendNotCalledOnMetaEdit() throws Exception { 886 final TableName tableName = 887 TableName.valueOf(TEST_TABLE.getNameAsString() + "." + currentTestName); 888 TableDescriptorBuilder tdBuilder = TableDescriptorBuilder.newBuilder(tableName); 889 ColumnFamilyDescriptorBuilder cfBuilder = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY); 890 tdBuilder.setColumnFamily(cfBuilder.build()); 891 tdBuilder.setCoprocessor(SimpleRegionObserver.class.getName()); 892 TableDescriptor td = tdBuilder.build(); 893 Table table = util.createTable(td, new byte[][] { A, B, C }); 894 895 PreWALAppendWALActionsListener listener = new PreWALAppendWALActionsListener(); 896 List<HRegion> regions = util.getHBaseCluster().getRegions(tableName); 897 // should be only one region 898 HRegion region = regions.get(0); 899 900 region.getWAL().registerWALActionsListener(listener); 901 // flushing should write to the WAL 902 region.flush(true); 903 // so should compaction 904 region.compact(false); 905 // and so should closing the region 906 region.close(); 907 908 // but we still shouldn't have triggered preWALAppend because no user data was written 909 String[] methods = new String[] { "getCtPreWALAppend" }; 910 Object[] expectedResult = new Integer[] { 0 }; 911 verifyMethodResult(SimpleRegionObserver.class, methods, tableName, expectedResult); 912 } 913 914 // check each region whether the coprocessor upcalls are called or not. 915 private void verifyMethodResult(Class<?> coprocessor, String methodName[], TableName tableName, 916 Object value[]) throws IOException { 917 try { 918 for (JVMClusterUtil.RegionServerThread t : cluster.getRegionServerThreads()) { 919 if (!t.isAlive() || t.getRegionServer().isAborted() || t.getRegionServer().isStopping()) { 920 continue; 921 } 922 for (RegionInfo r : ProtobufUtil.getOnlineRegions(t.getRegionServer().getRSRpcServices())) { 923 if (!r.getTable().equals(tableName)) { 924 continue; 925 } 926 RegionCoprocessorHost cph = 927 t.getRegionServer().getOnlineRegion(r.getRegionName()).getCoprocessorHost(); 928 929 Coprocessor cp = cph.findCoprocessor(coprocessor.getName()); 930 assertNotNull(cp); 931 for (int i = 0; i < methodName.length; ++i) { 932 Method m = coprocessor.getMethod(methodName[i]); 933 Object o = m.invoke(cp); 934 assertTrue(o.equals(value[i]), 935 "Result of " + coprocessor.getName() + "." + methodName[i] + " is expected to be " 936 + value[i].toString() + ", while we get " + o.toString()); 937 } 938 } 939 } 940 } catch (Exception e) { 941 throw new IOException(e.toString()); 942 } 943 } 944 945 private static void createHFile(Configuration conf, FileSystem fs, Path path, byte[] family, 946 byte[] qualifier) throws IOException { 947 HFileContext context = new HFileContextBuilder().build(); 948 HFile.Writer writer = HFile.getWriterFactory(conf, new CacheConfig(conf)).withPath(fs, path) 949 .withFileContext(context).create(); 950 long now = EnvironmentEdgeManager.currentTime(); 951 try { 952 for (int i = 1; i <= 9; i++) { 953 KeyValue kv = 954 new KeyValue(Bytes.toBytes(i + ""), family, qualifier, now, Bytes.toBytes(i + "")); 955 writer.append(kv); 956 } 957 } finally { 958 writer.close(); 959 } 960 } 961 962 private static class PreWALAppendWALActionsListener implements WALActionsListener { 963 boolean[] walKeysCorrect = { false, false, false, false }; 964 965 @Override 966 public void postAppend(long entryLen, long elapsedTimeMillis, WALKey logKey, WALEdit logEdit) 967 throws IOException { 968 for (int k = 0; k < 4; k++) { 969 if (!walKeysCorrect[k]) { 970 walKeysCorrect[k] = Arrays.equals(SimpleRegionObserver.WAL_EXTENDED_ATTRIBUTE_BYTES, 971 logKey.getExtendedAttribute(Integer.toString(k + 1))); 972 } 973 } 974 } 975 976 boolean[] getWalKeysCorrectArray() { 977 return walKeysCorrect; 978 } 979 } 980}