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