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