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}