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
246 verifyMethodResult(SimpleRegionObserver.class,
247 new String[] {"hadPreGet", "hadPostGet", "wasScannerNextCalled",
248 "wasScannerCloseCalled"},
249 tableName,
250 new Boolean[] {false, false, false, false}
251 );
252
253 HTable table = new HTable(util.getConfiguration(), tableName);
254 Put put = new Put(ROW);
255 put.add(A, A, A);
256 table.put(put);
257
258 Get get = new Get(ROW);
259 get.addColumn(A, A);
260 table.get(get);
261
262
263
264 verifyMethodResult(SimpleRegionObserver.class,
265 new String[] {"hadPreGet", "hadPostGet", "wasScannerNextCalled",
266 "wasScannerCloseCalled"},
267 tableName,
268 new Boolean[] {true, true, false, false}
269 );
270
271 Scan s = new Scan();
272 ResultScanner scanner = table.getScanner(s);
273 try {
274 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
275 }
276 } finally {
277 scanner.close();
278 }
279
280
281 verifyMethodResult(SimpleRegionObserver.class,
282 new String[] {"wasScannerNextCalled", "wasScannerCloseCalled"},
283 tableName,
284 new Boolean[] {true, true}
285 );
286 util.deleteTable(tableName);
287 table.close();
288 }
289
290 @Test
291
292 public void testHBase3758() throws IOException {
293 TableName tableName =
294 TableName.valueOf("testHBase3758");
295 util.createTable(tableName, new byte[][] {A, B, C});
296
297 verifyMethodResult(SimpleRegionObserver.class,
298 new String[] {"hadDeleted", "wasScannerOpenCalled"},
299 tableName,
300 new Boolean[] {false, false}
301 );
302
303 HTable table = new HTable(util.getConfiguration(), tableName);
304 Put put = new Put(ROW);
305 put.add(A, A, A);
306 table.put(put);
307
308 Delete delete = new Delete(ROW);
309 table.delete(delete);
310
311 verifyMethodResult(SimpleRegionObserver.class,
312 new String[] {"hadDeleted", "wasScannerOpenCalled"},
313 tableName,
314 new Boolean[] {true, false}
315 );
316
317 Scan s = new Scan();
318 ResultScanner scanner = table.getScanner(s);
319 try {
320 for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
321 }
322 } finally {
323 scanner.close();
324 }
325
326
327 verifyMethodResult(SimpleRegionObserver.class,
328 new String[] {"wasScannerOpenCalled"},
329 tableName,
330 new Boolean[] {true}
331 );
332 util.deleteTable(tableName);
333 table.close();
334 }
335
336
337 public static class EvenOnlyCompactor extends BaseRegionObserver {
338 long lastCompaction;
339 long lastFlush;
340
341 @Override
342 public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
343 Store store, final InternalScanner scanner, final ScanType scanType) {
344 return new InternalScanner() {
345 @Override
346 public boolean next(List<Cell> results) throws IOException {
347 return next(results, -1);
348 }
349
350 @Override
351 public boolean next(List<Cell> results, int limit)
352 throws IOException{
353 List<Cell> internalResults = new ArrayList<Cell>();
354 boolean hasMore;
355 do {
356 hasMore = scanner.next(internalResults, limit);
357 if (!internalResults.isEmpty()) {
358 long row = Bytes.toLong(CellUtil.cloneValue(internalResults.get(0)));
359 if (row % 2 == 0) {
360
361 break;
362 }
363
364 internalResults.clear();
365 }
366 } while (hasMore);
367
368 if (!internalResults.isEmpty()) {
369 results.addAll(internalResults);
370 }
371 return hasMore;
372 }
373
374 @Override
375 public void close() throws IOException {
376 scanner.close();
377 }
378 };
379 }
380
381 @Override
382 public void postCompact(ObserverContext<RegionCoprocessorEnvironment> e,
383 Store store, StoreFile resultFile) {
384 lastCompaction = EnvironmentEdgeManager.currentTimeMillis();
385 }
386
387 @Override
388 public void postFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
389 lastFlush = EnvironmentEdgeManager.currentTimeMillis();
390 }
391 }
392
393
394
395
396 @Test
397 public void testCompactionOverride() throws Exception {
398 byte[] compactTable = Bytes.toBytes("TestCompactionOverride");
399 HBaseAdmin admin = util.getHBaseAdmin();
400 if (admin.tableExists(compactTable)) {
401 admin.disableTable(compactTable);
402 admin.deleteTable(compactTable);
403 }
404
405 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(compactTable));
406 htd.addFamily(new HColumnDescriptor(A));
407 htd.addCoprocessor(EvenOnlyCompactor.class.getName());
408 admin.createTable(htd);
409
410 HTable table = new HTable(util.getConfiguration(), compactTable);
411 for (long i=1; i<=10; i++) {
412 byte[] iBytes = Bytes.toBytes(i);
413 Put put = new Put(iBytes);
414 put.setDurability(Durability.SKIP_WAL);
415 put.add(A, A, iBytes);
416 table.put(put);
417 }
418
419 HRegion firstRegion = cluster.getRegions(compactTable).get(0);
420 Coprocessor cp = firstRegion.getCoprocessorHost().findCoprocessor(
421 EvenOnlyCompactor.class.getName());
422 assertNotNull("EvenOnlyCompactor coprocessor should be loaded", cp);
423 EvenOnlyCompactor compactor = (EvenOnlyCompactor)cp;
424
425
426 long ts = System.currentTimeMillis();
427 admin.flush(compactTable);
428
429 for (int i=0; i<10; i++) {
430 if (compactor.lastFlush >= ts) {
431 break;
432 }
433 Thread.sleep(1000);
434 }
435 assertTrue("Flush didn't complete", compactor.lastFlush >= ts);
436 LOG.debug("Flush complete");
437
438 ts = compactor.lastFlush;
439 admin.majorCompact(compactTable);
440
441 for (int i=0; i<30; i++) {
442 if (compactor.lastCompaction >= ts) {
443 break;
444 }
445 Thread.sleep(1000);
446 }
447 LOG.debug("Last compaction was at "+compactor.lastCompaction);
448 assertTrue("Compaction didn't complete", compactor.lastCompaction >= ts);
449
450
451 ResultScanner scanner = table.getScanner(new Scan());
452 try {
453 for (long i=2; i<=10; i+=2) {
454 Result r = scanner.next();
455 assertNotNull(r);
456 assertFalse(r.isEmpty());
457 byte[] iBytes = Bytes.toBytes(i);
458 assertArrayEquals("Row should be "+i, r.getRow(), iBytes);
459 assertArrayEquals("Value should be "+i, r.getValue(A, A), iBytes);
460 }
461 } finally {
462 scanner.close();
463 }
464 table.close();
465 }
466
467 @Test
468 public void bulkLoadHFileTest() throws Exception {
469 String testName = TestRegionObserverInterface.class.getName()+".bulkLoadHFileTest";
470 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".bulkLoadHFileTest");
471 Configuration conf = util.getConfiguration();
472 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
473 try {
474 verifyMethodResult(SimpleRegionObserver.class,
475 new String[] {"hadPreBulkLoadHFile", "hadPostBulkLoadHFile"},
476 tableName,
477 new Boolean[] {false, false}
478 );
479
480 FileSystem fs = util.getTestFileSystem();
481 final Path dir = util.getDataTestDirOnTestFS(testName).makeQualified(fs);
482 Path familyDir = new Path(dir, Bytes.toString(A));
483
484 createHFile(util.getConfiguration(), fs, new Path(familyDir,Bytes.toString(A)), A, A);
485
486
487 new LoadIncrementalHFiles(conf).doBulkLoad(dir, new HTable(conf, tableName));
488
489 verifyMethodResult(SimpleRegionObserver.class,
490 new String[] {"hadPreBulkLoadHFile", "hadPostBulkLoadHFile"},
491 tableName,
492 new Boolean[] {true, true}
493 );
494 } finally {
495 util.deleteTable(tableName);
496 table.close();
497 }
498 }
499
500 @Test
501 public void testRecovery() throws Exception {
502 LOG.info(TestRegionObserverInterface.class.getName() +".testRecovery");
503 TableName tableName = TableName.valueOf(TEST_TABLE.getNameAsString() + ".testRecovery");
504 HTable table = util.createTable(tableName, new byte[][] {A, B, C});
505 try {
506 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer();
507 ServerName sn2 = rs1.getRegionServer().getServerName();
508 String regEN = table.getRegionLocations().firstEntry().getKey().getEncodedName();
509
510 util.getHBaseAdmin().move(regEN.getBytes(), sn2.getServerName().getBytes());
511 while (!sn2.equals(table.getRegionLocations().firstEntry().getValue() )){
512 Thread.sleep(100);
513 }
514
515 Put put = new Put(ROW);
516 put.add(A, A, A);
517 put.add(B, B, B);
518 put.add(C, C, C);
519 table.put(put);
520
521 verifyMethodResult(SimpleRegionObserver.class,
522 new String[] {"hadPreGet", "hadPostGet", "hadPrePut", "hadPostPut",
523 "hadPreBatchMutate", "hadPostBatchMutate", "hadDelete"},
524 tableName,
525 new Boolean[] {false, false, true, true, true, true, false}
526 );
527
528 verifyMethodResult(SimpleRegionObserver.class,
529 new String[] {"getCtPreWALRestore", "getCtPostWALRestore", "getCtPrePut", "getCtPostPut"},
530 tableName,
531 new Integer[] {0, 0, 1, 1});
532
533 cluster.killRegionServer(rs1.getRegionServer().getServerName());
534 Threads.sleep(1000);
535 util.waitUntilAllRegionsAssigned(tableName);
536 LOG.info("All regions assigned");
537
538 verifyMethodResult(SimpleRegionObserver.class,
539 new String[]{"getCtPrePut", "getCtPostPut"},
540 tableName,
541 new Integer[]{0, 0});
542 } finally {
543 util.deleteTable(tableName);
544 table.close();
545 }
546 }
547
548 @Test
549 public void testPreWALRestoreSkip() throws Exception {
550 LOG.info(TestRegionObserverInterface.class.getName() + ".testPreWALRestoreSkip");
551 TableName tableName = TableName.valueOf(SimpleRegionObserver.TABLE_SKIPPED);
552 HTable table = util.createTable(tableName, new byte[][] { A, B, C });
553
554 JVMClusterUtil.RegionServerThread rs1 = cluster.startRegionServer();
555 ServerName sn2 = rs1.getRegionServer().getServerName();
556 String regEN = table.getRegionLocations().firstEntry().getKey().getEncodedName();
557
558 util.getHBaseAdmin().move(regEN.getBytes(), sn2.getServerName().getBytes());
559 while (!sn2.equals(table.getRegionLocations().firstEntry().getValue())) {
560 Thread.sleep(100);
561 }
562
563 Put put = new Put(ROW);
564 put.add(A, A, A);
565 put.add(B, B, B);
566 put.add(C, C, C);
567 table.put(put);
568 table.flushCommits();
569
570 cluster.killRegionServer(rs1.getRegionServer().getServerName());
571 Threads.sleep(20000);
572 util.waitUntilAllRegionsAssigned(tableName);
573
574 verifyMethodResult(SimpleRegionObserver.class, new String[] { "getCtPreWALRestore",
575 "getCtPostWALRestore" }, tableName, new Integer[] { 0, 0 });
576
577 util.deleteTable(tableName);
578 table.close();
579 }
580
581
582 private void verifyMethodResult(Class<?> c, String methodName[], TableName tableName,
583 Object value[]) throws IOException {
584 try {
585 for (JVMClusterUtil.RegionServerThread t : cluster.getRegionServerThreads()) {
586 if (!t.isAlive() || t.getRegionServer().isAborted() || t.getRegionServer().isStopping()){
587 continue;
588 }
589 for (HRegionInfo r : ProtobufUtil.getOnlineRegions(t.getRegionServer())) {
590 if (!r.getTable().equals(tableName)) {
591 continue;
592 }
593 RegionCoprocessorHost cph = t.getRegionServer().getOnlineRegion(r.getRegionName()).
594 getCoprocessorHost();
595
596 Coprocessor cp = cph.findCoprocessor(c.getName());
597 assertNotNull(cp);
598 for (int i = 0; i < methodName.length; ++i) {
599 Method m = c.getMethod(methodName[i]);
600 Object o = m.invoke(cp);
601 assertTrue("Result of " + c.getName() + "." + methodName[i]
602 + " is expected to be " + value[i].toString()
603 + ", while we get " + o.toString(), o.equals(value[i]));
604 }
605 }
606 }
607 } catch (Exception e) {
608 throw new IOException(e.toString());
609 }
610 }
611
612 private static void createHFile(
613 Configuration conf,
614 FileSystem fs, Path path,
615 byte[] family, byte[] qualifier) throws IOException {
616 HFileContext context = new HFileContextBuilder().build();
617 HFile.Writer writer = HFile.getWriterFactory(conf, new CacheConfig(conf))
618 .withPath(fs, path)
619 .withFileContext(context)
620 .create();
621 long now = System.currentTimeMillis();
622 try {
623 for (int i =1;i<=9;i++) {
624 KeyValue kv = new KeyValue(Bytes.toBytes(i+""), family, qualifier, now, Bytes.toBytes(i+""));
625 writer.append(kv);
626 }
627 } finally {
628 writer.close();
629 }
630 }
631
632 }