1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.coprocessor;
21
22 import static org.junit.Assert.assertArrayEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26
27 import java.io.IOException;
28 import java.lang.reflect.Method;
29 import java.util.ArrayList;
30 import java.util.List;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.hbase.Cell;
38 import org.apache.hadoop.hbase.CellUtil;
39 import org.apache.hadoop.hbase.Coprocessor;
40 import org.apache.hadoop.hbase.HBaseTestingUtility;
41 import org.apache.hadoop.hbase.HColumnDescriptor;
42 import org.apache.hadoop.hbase.HRegionInfo;
43 import org.apache.hadoop.hbase.HTableDescriptor;
44 import org.apache.hadoop.hbase.KeyValue;
45 import org.apache.hadoop.hbase.MediumTests;
46 import org.apache.hadoop.hbase.MiniHBaseCluster;
47 import org.apache.hadoop.hbase.ServerName;
48 import org.apache.hadoop.hbase.TableName;
49 import org.apache.hadoop.hbase.client.Delete;
50 import org.apache.hadoop.hbase.client.Durability;
51 import org.apache.hadoop.hbase.client.Get;
52 import org.apache.hadoop.hbase.client.HBaseAdmin;
53 import org.apache.hadoop.hbase.client.HTable;
54 import org.apache.hadoop.hbase.client.Increment;
55 import org.apache.hadoop.hbase.client.Put;
56 import org.apache.hadoop.hbase.client.Result;
57 import org.apache.hadoop.hbase.client.ResultScanner;
58 import org.apache.hadoop.hbase.client.RowMutations;
59 import org.apache.hadoop.hbase.client.Scan;
60 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
61 import org.apache.hadoop.hbase.io.hfile.HFile;
62 import org.apache.hadoop.hbase.io.hfile.HFileContext;
63 import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
64 import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
65 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
66 import org.apache.hadoop.hbase.regionserver.HRegion;
67 import org.apache.hadoop.hbase.regionserver.InternalScanner;
68 import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
69 import org.apache.hadoop.hbase.regionserver.ScanType;
70 import org.apache.hadoop.hbase.regionserver.Store;
71 import org.apache.hadoop.hbase.regionserver.StoreFile;
72 import org.apache.hadoop.hbase.util.Bytes;
73 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
74 import org.apache.hadoop.hbase.util.JVMClusterUtil;
75 import org.apache.hadoop.hbase.util.Threads;
76 import org.junit.AfterClass;
77 import org.junit.BeforeClass;
78 import org.junit.Test;
79 import org.junit.experimental.categories.Category;
80
81 @Category(MediumTests.class)
82 public class TestRegionObserverInterface {
83 static final Log LOG = LogFactory.getLog(TestRegionObserverInterface.class);
84
85 public static final TableName TEST_TABLE = TableName.valueOf("TestTable");
86 public final static byte[] A = Bytes.toBytes("a");
87 public final static byte[] B = Bytes.toBytes("b");
88 public final static byte[] C = Bytes.toBytes("c");
89 public final static byte[] ROW = Bytes.toBytes("testrow");
90
91 private static HBaseTestingUtility util = new HBaseTestingUtility();
92 private static MiniHBaseCluster cluster = null;
93
94 @BeforeClass
95 public static void setupBeforeClass() throws Exception {
96
97 Configuration conf = util.getConfiguration();
98 conf.setBoolean("hbase.master.distributed.log.replay", true);
99 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
100 "org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver");
101
102 util.startMiniCluster();
103 cluster = util.getMiniHBaseCluster();
104 }
105
106 @AfterClass
107 public static void tearDownAfterClass() throws Exception {
108 util.shutdownMiniCluster();
109 }
110
111 @Test
112 public void testRegionObserver() throws IOException {
113 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRegionObserver");
114
115
116 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
117 try {
118 verifyMethodResult(SimpleRegionObserver.class, new String[] { "hadPreGet", "hadPostGet",
119 "hadPrePut", "hadPostPut", "hadDelete", "hadPostStartRegionOperation",
120 "hadPostCloseRegionOperation", "hadPostBatchMutateIndispensably" }, tableName,
121 new Boolean[] { false, false, false, false, false, false, false, false });
122
123 Put put = new Put(ROW);
124 put.add(A, A, A);
125 put.add(B, B, B);
126 put.add(C, C, C);
127 table.put(put);
128
129 verifyMethodResult(SimpleRegionObserver.class, new String[] { "hadPreGet", "hadPostGet",
130 "hadPrePut", "hadPostPut", "hadPreBatchMutate", "hadPostBatchMutate", "hadDelete",
131 "hadPostStartRegionOperation", "hadPostCloseRegionOperation",
132 "hadPostBatchMutateIndispensably" }, TEST_TABLE, new Boolean[] { false, false, true,
133 true, true, true, false, true, true, true });
134
135 verifyMethodResult(SimpleRegionObserver.class,
136 new String[] {"getCtPreOpen", "getCtPostOpen", "getCtPreClose", "getCtPostClose"},
137 tableName,
138 new Integer[] {1, 1, 0, 0});
139
140 Get get = new Get(ROW);
141 get.addColumn(A, A);
142 get.addColumn(B, B);
143 get.addColumn(C, C);
144 table.get(get);
145
146 verifyMethodResult(SimpleRegionObserver.class,
147 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
148 "hadDelete"},
149 tableName,
150 new Boolean[] {true, true, true, true, false}
151 );
152
153 Delete delete = new Delete(ROW);
154 delete.deleteColumn(A, A);
155 delete.deleteColumn(B, B);
156 delete.deleteColumn(C, C);
157 table.delete(delete);
158
159 verifyMethodResult(SimpleRegionObserver.class,
160 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
161 "hadPreBatchMutate", "hadPostBatchMutate", "hadDelete"},
162 tableName,
163 new Boolean[] {true, true, true, true, true, true, true}
164 );
165 } finally {
166 util.deleteTable(tableName);
167 table.close();
168 }
169 verifyMethodResult(SimpleRegionObserver.class,
170 new String[] {"getCtPreOpen", "getCtPostOpen", "getCtPreClose", "getCtPostClose"},
171 tableName,
172 new Integer[] {1, 1, 1, 1});
173 }
174
175 @Test
176 public void testRowMutation() throws IOException {
177 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRowMutation");
178 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
179 try {
180 verifyMethodResult(SimpleRegionObserver.class,
181 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
182 "hadDeleted"},
183 tableName,
184 new Boolean[] {false, false, false, false, false});
185 Put put = new Put(ROW);
186 put.add(A, A, A);
187 put.add(B, B, B);
188 put.add(C, C, C);
189
190 Delete delete = new Delete(ROW);
191 delete.deleteColumn(A, A);
192 delete.deleteColumn(B, B);
193 delete.deleteColumn(C, C);
194
195 RowMutations arm = new RowMutations(ROW);
196 arm.add(put);
197 arm.add(delete);
198 table.mutateRow(arm);
199
200 verifyMethodResult(SimpleRegionObserver.class,
201 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
202 "hadDeleted"},
203 tableName,
204 new Boolean[] {false, false, true, true, true}
205 );
206 } finally {
207 util.deleteTable(tableName);
208 table.close();
209 }
210 }
211
212 @Test
213 public void testIncrementHook() throws IOException {
214 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testIncrementHook");
215 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
216 try {
217 Increment inc = new Increment(Bytes.toBytes(0));
218 inc.addColumn(A, A, 1);
219
220 verifyMethodResult(SimpleRegionObserver.class,
221 new String[] {"hadPreIncrement", "hadPostIncrement"},
222 tableName,
223 new Boolean[] {false, false}
224 );
225
226 table.increment(inc);
227
228 verifyMethodResult(SimpleRegionObserver.class,
229 new String[] {"hadPreIncrement", "hadPostIncrement"},
230 tableName,
231 new Boolean[] {true, true}
232 );
233 } finally {
234 util.deleteTable(tableName);
235 table.close();
236 }
237 }
238
239 @Test
240
241 public void testHBase3583() throws IOException {
242 TableName tableName =
243 TableName.valueOf("testHBase3583");
244 util.createTable(tableName, new byte[][] {A, B, C});
245 util.waitUntilAllRegionsAssigned(tableName);
246
247 verifyMethodResult(SimpleRegionObserver.class,
248 new String[] {"hadPreGet", "hadPostGet", "wasScannerNextCalled",
249 "wasScannerCloseCalled"},
250 tableName,
251 new Boolean[] {false, false, false, false}
252 );
253
254 HTable table = new HTable(util.getConfiguration(), tableName);
255 Put put = new Put(ROW);
256 put.add(A, A, A);
257 table.put(put);
258
259 Get get = new Get(ROW);
260 get.addColumn(A, A);
261 table.get(get);
262
263
264
265 verifyMethodResult(SimpleRegionObserver.class,
266 new String[] {"hadPreGet", "hadPostGet", "wasScannerNextCalled",
267 "wasScannerCloseCalled"},
268 tableName,
269 new Boolean[] {true, true, false, false}
270 );
271
272 Scan s = new Scan();
273 ResultScanner scanner = table.getScanner(s);
274 try {
275 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
276 }
277 } finally {
278 scanner.close();
279 }
280
281
282 verifyMethodResult(SimpleRegionObserver.class,
283 new String[] {"wasScannerNextCalled", "wasScannerCloseCalled"},
284 tableName,
285 new Boolean[] {true, true}
286 );
287 util.deleteTable(tableName);
288 table.close();
289 }
290
291 @Test
292
293 public void testHBase3758() throws IOException {
294 TableName tableName =
295 TableName.valueOf("testHBase3758");
296 util.createTable(tableName, new byte[][] {A, B, C});
297
298 verifyMethodResult(SimpleRegionObserver.class,
299 new String[] {"hadDeleted", "wasScannerOpenCalled"},
300 tableName,
301 new Boolean[] {false, false}
302 );
303
304 HTable table = new HTable(util.getConfiguration(), tableName);
305 Put put = new Put(ROW);
306 put.add(A, A, A);
307 table.put(put);
308
309 Delete delete = new Delete(ROW);
310 table.delete(delete);
311
312 verifyMethodResult(SimpleRegionObserver.class,
313 new String[] {"hadDeleted", "wasScannerOpenCalled"},
314 tableName,
315 new Boolean[] {true, false}
316 );
317
318 Scan s = new Scan();
319 ResultScanner scanner = table.getScanner(s);
320 try {
321 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
322 }
323 } finally {
324 scanner.close();
325 }
326
327
328 verifyMethodResult(SimpleRegionObserver.class,
329 new String[] {"wasScannerOpenCalled"},
330 tableName,
331 new Boolean[] {true}
332 );
333 util.deleteTable(tableName);
334 table.close();
335 }
336
337
338 public static class EvenOnlyCompactor extends BaseRegionObserver {
339 long lastCompaction;
340 long lastFlush;
341
342 @Override
343 public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
344 Store store, final InternalScanner scanner, final ScanType scanType) {
345 return new InternalScanner() {
346 @Override
347 public boolean next(List<Cell> results) throws IOException {
348 return next(results, -1);
349 }
350
351 @Override
352 public boolean next(List<Cell> results, int limit)
353 throws IOException{
354 List<Cell> internalResults = new ArrayList<Cell>();
355 boolean hasMore;
356 do {
357 hasMore = scanner.next(internalResults, limit);
358 if (!internalResults.isEmpty()) {
359 long row = Bytes.toLong(CellUtil.cloneValue(internalResults.get(0)));
360 if (row % 2 == 0) {
361
362 break;
363 }
364
365 internalResults.clear();
366 }
367 } while (hasMore);
368
369 if (!internalResults.isEmpty()) {
370 results.addAll(internalResults);
371 }
372 return hasMore;
373 }
374
375 @Override
376 public void close() throws IOException {
377 scanner.close();
378 }
379 };
380 }
381
382 @Override
383 public void postCompact(ObserverContext<RegionCoprocessorEnvironment> e,
384 Store store, StoreFile resultFile) {
385 lastCompaction = EnvironmentEdgeManager.currentTimeMillis();
386 }
387
388 @Override
389 public void postFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
390 lastFlush = EnvironmentEdgeManager.currentTimeMillis();
391 }
392 }
393
394
395
396
397 @Test
398 public void testCompactionOverride() throws Exception {
399 byte[] compactTable = Bytes.toBytes("TestCompactionOverride");
400 HBaseAdmin admin = util.getHBaseAdmin();
401 if (admin.tableExists(compactTable)) {
402 admin.disableTable(compactTable);
403 admin.deleteTable(compactTable);
404 }
405
406 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(compactTable));
407 htd.addFamily(new HColumnDescriptor(A));
408 htd.addCoprocessor(EvenOnlyCompactor.class.getName());
409 admin.createTable(htd);
410
411 HTable table = new HTable(util.getConfiguration(), compactTable);
412 for (long i=1; i<=10; i++) {
413 byte[] iBytes = Bytes.toBytes(i);
414 Put put = new Put(iBytes);
415 put.setDurability(Durability.SKIP_WAL);
416 put.add(A, A, iBytes);
417 table.put(put);
418 }
419
420 HRegion firstRegion = cluster.getRegions(compactTable).get(0);
421 Coprocessor cp = firstRegion.getCoprocessorHost().findCoprocessor(
422 EvenOnlyCompactor.class.getName());
423 assertNotNull("EvenOnlyCompactor coprocessor should be loaded", cp);
424 EvenOnlyCompactor compactor = (EvenOnlyCompactor)cp;
425
426
427 long ts = System.currentTimeMillis();
428 admin.flush(compactTable);
429
430 for (int i=0; i<10; i++) {
431 if (compactor.lastFlush >= ts) {
432 break;
433 }
434 Thread.sleep(1000);
435 }
436 assertTrue("Flush didn't complete", compactor.lastFlush >= ts);
437 LOG.debug("Flush complete");
438
439 ts = compactor.lastFlush;
440 admin.majorCompact(compactTable);
441
442 for (int i=0; i<30; i++) {
443 if (compactor.lastCompaction >= ts) {
444 break;
445 }
446 Thread.sleep(1000);
447 }
448 LOG.debug("Last compaction was at "+compactor.lastCompaction);
449 assertTrue("Compaction didn't complete", compactor.lastCompaction >= ts);
450
451
452 ResultScanner scanner = table.getScanner(new Scan());
453 try {
454 for (long i=2; i<=10; i+=2) {
455 Result r = scanner.next();
456 assertNotNull(r);
457 assertFalse(r.isEmpty());
458 byte[] iBytes = Bytes.toBytes(i);
459 assertArrayEquals("Row should be "+i, r.getRow(), iBytes);
460 assertArrayEquals("Value should be "+i, r.getValue(A, A), iBytes);
461 }
462 } finally {
463 scanner.close();
464 }
465 table.close();
466 }
467
468 @Test
469 public void bulkLoadHFileTest() throws Exception {
470 String testName = TestRegionObserverInterface.class.getName()+".bulkLoadHFileTest";
471 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".bulkLoadHFileTest");
472 Configuration conf = util.getConfiguration();
473 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
474 try {
475 verifyMethodResult(SimpleRegionObserver.class,
476 new String[] {"hadPreBulkLoadHFile", "hadPostBulkLoadHFile"},
477 tableName,
478 new Boolean[] {false, false}
479 );
480
481 FileSystem fs = util.getTestFileSystem();
482 final Path dir = util.getDataTestDirOnTestFS(testName).makeQualified(fs);
483 Path familyDir = new Path(dir, Bytes.toString(A));
484
485 createHFile(util.getConfiguration(), fs, new Path(familyDir,Bytes.toString(A)), A, A);
486
487
488 new LoadIncrementalHFiles(conf).doBulkLoad(dir, new HTable(conf, tableName));
489
490 verifyMethodResult(SimpleRegionObserver.class,
491 new String[] {"hadPreBulkLoadHFile", "hadPostBulkLoadHFile"},
492 tableName,
493 new Boolean[] {true, true}
494 );
495 } finally {
496 util.deleteTable(tableName);
497 table.close();
498 }
499 }
500
501 @Test
502 public void testRecovery() throws Exception {
503 LOG.info(TestRegionObserverInterface.class.getName() +".testRecovery");
504 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRecovery");
505 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
506 try {
507 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer();
508 ServerName sn2 = rs1.getRegionServer().getServerName();
509 String regEN = table.getRegionLocations().firstEntry().getKey().getEncodedName();
510
511 util.getHBaseAdmin().move(regEN.getBytes(), sn2.getServerName().getBytes());
512 while (!sn2.equals(table.getRegionLocations().firstEntry().getValue() )){
513 Thread.sleep(100);
514 }
515
516 Put put = new Put(ROW);
517 put.add(A, A, A);
518 put.add(B, B, B);
519 put.add(C, C, C);
520 table.put(put);
521
522 verifyMethodResult(SimpleRegionObserver.class,
523 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
524 "hadPreBatchMutate", "hadPostBatchMutate", "hadDelete"},
525 tableName,
526 new Boolean[] {false, false, true, true, true, true, false}
527 );
528
529 verifyMethodResult(SimpleRegionObserver.class,
530 new String[] {"getCtPreWALRestore", "getCtPostWALRestore", "getCtPrePut", "getCtPostPut"},
531 tableName,
532 new Integer[] {0, 0, 1, 1});
533
534 cluster.killRegionServer(rs1.getRegionServer().getServerName());
535 Threads.sleep(1000);
536 util.waitUntilAllRegionsAssigned(tableName);
537 LOG.info("All regions assigned");
538
539 verifyMethodResult(SimpleRegionObserver.class,
540 new String[]{"getCtPrePut", "getCtPostPut"},
541 tableName,
542 new Integer[]{0, 0});
543 } finally {
544 util.deleteTable(tableName);
545 table.close();
546 }
547 }
548
549 @Test
550 public void testPreWALRestoreSkip() throws Exception {
551 LOG.info(TestRegionObserverInterface.class.getName() + ".testPreWALRestoreSkip");
552 TableName tableName = TableName.valueOf(SimpleRegionObserver.TABLE_SKIPPED);
553 HTable table = util.createTable(tableName, new byte[][] { A, B, C });
554
555 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer();
556 ServerName sn2 = rs1.getRegionServer().getServerName();
557 String regEN = table.getRegionLocations().firstEntry().getKey().getEncodedName();
558
559 util.getHBaseAdmin().move(regEN.getBytes(), sn2.getServerName().getBytes());
560 while (!sn2.equals(table.getRegionLocations().firstEntry().getValue())) {
561 Thread.sleep(100);
562 }
563
564 Put put = new Put(ROW);
565 put.add(A, A, A);
566 put.add(B, B, B);
567 put.add(C, C, C);
568 table.put(put);
569 table.flushCommits();
570
571 cluster.killRegionServer(rs1.getRegionServer().getServerName());
572 Threads.sleep(20000);
573 util.waitUntilAllRegionsAssigned(tableName);
574
575 verifyMethodResult(SimpleRegionObserver.class, new String[] { "getCtPreWALRestore",
576 "getCtPostWALRestore" }, tableName, new Integer[] { 0, 0 });
577
578 util.deleteTable(tableName);
579 table.close();
580 }
581
582
583 private void verifyMethodResult(Class<?> c, String methodName[], TableName tableName,
584 Object value[]) throws IOException {
585 try {
586 for (JVMClusterUtil.RegionServerThread t : cluster.getRegionServerThreads()) {
587 if (!t.isAlive() || t.getRegionServer().isAborted() || t.getRegionServer().isStopping()){
588 continue;
589 }
590 for (HRegionInfo r : ProtobufUtil.getOnlineRegions(t.getRegionServer())) {
591 if (!r.getTable().equals(tableName)) {
592 continue;
593 }
594 RegionCoprocessorHost cph = t.getRegionServer().getOnlineRegion(r.getRegionName()).
595 getCoprocessorHost();
596
597 Coprocessor cp = cph.findCoprocessor(c.getName());
598 assertNotNull(cp);
599 for (int i = 0; i < methodName.length; ++i) {
600 Method m = c.getMethod(methodName[i]);
601 Object o = m.invoke(cp);
602 assertTrue("Result of " + c.getName() + "." + methodName[i]
603 + " is expected to be " + value[i].toString()
604 + ", while we get " + o.toString(), o.equals(value[i]));
605 }
606 }
607 }
608 } catch (Exception e) {
609 throw new IOException(e.toString());
610 }
611 }
612
613 private static void createHFile(
614 Configuration conf,
615 FileSystem fs, Path path,
616 byte[] family, byte[] qualifier) throws IOException {
617 HFileContext context = new HFileContextBuilder().build();
618 HFile.Writer writer = HFile.getWriterFactory(conf, new CacheConfig(conf))
619 .withPath(fs, path)
620 .withFileContext(context)
621 .create();
622 long now = System.currentTimeMillis();
623 try {
624 for (int i =1;i<=9;i++) {
625 KeyValue kv = new KeyValue(Bytes.toBytes(i+""), family, qualifier, now, Bytes.toBytes(i+""));
626 writer.append(kv);
627 }
628 } finally {
629 writer.close();
630 }
631 }
632
633 }