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}