1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.procedure2.store.wal;
20
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.util.Arrays;
26 import java.util.Comparator;
27 import java.util.HashSet;
28 import java.util.Set;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hadoop.fs.FileSystem;
33 import org.apache.hadoop.fs.FileStatus;
34 import org.apache.hadoop.fs.Path;
35 import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
36 import org.apache.hadoop.hbase.procedure2.Procedure;
37 import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
38 import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.LoadCounter;
39 import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.TestProcedure;
40 import org.apache.hadoop.hbase.procedure2.SequentialProcedure;
41 import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
42 import org.apache.hadoop.hbase.procedure2.store.ProcedureStore.ProcedureIterator;
43 import org.apache.hadoop.hbase.testclassification.SmallTests;
44 import org.apache.hadoop.hbase.util.Bytes;
45 import org.apache.hadoop.io.IOUtils;
46
47 import org.junit.After;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.experimental.categories.Category;
51
52 import static org.junit.Assert.assertEquals;
53 import static org.junit.Assert.assertFalse;
54 import static org.junit.Assert.assertTrue;
55 import static org.junit.Assert.fail;
56
57 @Category(SmallTests.class)
58 public class TestWALProcedureStore {
59 private static final Log LOG = LogFactory.getLog(TestWALProcedureStore.class);
60
61 private static final int PROCEDURE_STORE_SLOTS = 1;
62
63 private WALProcedureStore procStore;
64
65 private HBaseCommonTestingUtility htu;
66 private FileSystem fs;
67 private Path testDir;
68 private Path logDir;
69
70 @Before
71 public void setUp() throws IOException {
72 htu = new HBaseCommonTestingUtility();
73 testDir = htu.getDataTestDir();
74 fs = testDir.getFileSystem(htu.getConfiguration());
75 assertTrue(testDir.depth() > 1);
76
77 logDir = new Path(testDir, "proc-logs");
78 procStore = ProcedureTestingUtility.createWalStore(htu.getConfiguration(), logDir);
79 procStore.start(PROCEDURE_STORE_SLOTS);
80 procStore.recoverLease();
81 procStore.load(new LoadCounter());
82 }
83
84 @After
85 public void tearDown() throws IOException {
86 procStore.stop(false);
87 fs.delete(logDir, true);
88 }
89
90 private void storeRestart(ProcedureStore.ProcedureLoader loader) throws Exception {
91 ProcedureTestingUtility.storeRestart(procStore, loader);
92 }
93
94 @Test
95 public void testEmptyRoll() throws Exception {
96 for (int i = 0; i < 10; ++i) {
97 procStore.periodicRollForTesting();
98 }
99 FileStatus[] status = fs.listStatus(logDir);
100 assertEquals(1, status.length);
101 }
102
103 @Test
104 public void testEmptyLogLoad() throws Exception {
105 LoadCounter loader = new LoadCounter();
106 storeRestart(loader);
107 assertEquals(0, loader.getMaxProcId());
108 assertEquals(0, loader.getLoadedCount());
109 assertEquals(0, loader.getCorruptedCount());
110 }
111
112 @Test
113 public void testLoad() throws Exception {
114 Set<Long> procIds = new HashSet<>();
115
116
117 Procedure proc1 = new TestSequentialProcedure();
118 procIds.add(proc1.getProcId());
119 procStore.insert(proc1, null);
120
121 Procedure proc2 = new TestSequentialProcedure();
122 Procedure[] child2 = new Procedure[2];
123 child2[0] = new TestSequentialProcedure();
124 child2[1] = new TestSequentialProcedure();
125
126 procIds.add(proc2.getProcId());
127 procIds.add(child2[0].getProcId());
128 procIds.add(child2[1].getProcId());
129 procStore.insert(proc2, child2);
130
131
132 verifyProcIdsOnRestart(procIds);
133
134
135 procStore.update(proc1);
136 procStore.update(child2[1]);
137 procStore.delete(child2[1].getProcId());
138 procIds.remove(child2[1].getProcId());
139
140
141 verifyProcIdsOnRestart(procIds);
142
143
144 procStore.stop(false);
145 FileStatus[] logs = fs.listStatus(logDir);
146 assertEquals(3, logs.length);
147 for (int i = 0; i < logs.length; ++i) {
148 corruptLog(logs[i], 4);
149 }
150 verifyProcIdsOnRestart(procIds);
151 }
152
153 @Test
154 public void testNoTrailerDoubleRestart() throws Exception {
155
156 Procedure proc0 = new TestSequentialProcedure();
157 procStore.insert(proc0, null);
158 Procedure proc1 = new TestSequentialProcedure();
159 procStore.insert(proc1, null);
160 Procedure proc2 = new TestSequentialProcedure();
161 procStore.insert(proc2, null);
162 procStore.rollWriterForTesting();
163
164
165 procStore.delete(proc1.getProcId());
166 procStore.rollWriterForTesting();
167
168
169 procStore.update(proc2);
170 procStore.rollWriterForTesting();
171
172
173 procStore.delete(proc2.getProcId());
174
175
176 procStore.stop(false);
177 FileStatus[] logs = fs.listStatus(logDir);
178 assertEquals(4, logs.length);
179 for (int i = 0; i < logs.length; ++i) {
180 corruptLog(logs[i], 4);
181 }
182
183
184 LoadCounter loader = new LoadCounter();
185 storeRestart(loader);
186 assertEquals(1, loader.getLoadedCount());
187 assertEquals(0, loader.getCorruptedCount());
188
189
190 assertEquals(5, fs.listStatus(logDir).length);
191 loader = new LoadCounter();
192 storeRestart(loader);
193 assertEquals(1, loader.getLoadedCount());
194 assertEquals(0, loader.getCorruptedCount());
195
196
197 procStore.delete(proc0.getProcId());
198 procStore.periodicRollForTesting();
199 assertEquals(1, fs.listStatus(logDir).length);
200 storeRestart(loader);
201 }
202
203 @Test
204 public void testCorruptedTrailer() throws Exception {
205
206 for (int i = 0; i < 100; ++i) {
207 procStore.insert(new TestSequentialProcedure(), null);
208 }
209
210
211 procStore.stop(false);
212
213
214 FileStatus[] logs = fs.listStatus(logDir);
215 assertEquals(1, logs.length);
216 corruptLog(logs[0], 4);
217
218 LoadCounter loader = new LoadCounter();
219 storeRestart(loader);
220 assertEquals(100, loader.getLoadedCount());
221 assertEquals(0, loader.getCorruptedCount());
222 }
223
224 @Test
225 public void testCorruptedEntries() throws Exception {
226
227 for (int i = 0; i < 100; ++i) {
228 procStore.insert(new TestSequentialProcedure(), null);
229 }
230
231
232 procStore.stop(false);
233
234
235
236 FileStatus[] logs = fs.listStatus(logDir);
237 assertEquals(1, logs.length);
238 corruptLog(logs[0], 1823);
239
240 LoadCounter loader = new LoadCounter();
241 storeRestart(loader);
242 assertTrue(procStore.getCorruptedLogs() != null);
243 assertEquals(1, procStore.getCorruptedLogs().size());
244 assertEquals(85, loader.getLoadedCount());
245 assertEquals(0, loader.getCorruptedCount());
246 }
247
248 @Test
249 public void testCorruptedProcedures() throws Exception {
250
251 TestProcedure[] rootProcs = new TestProcedure[10];
252 for (int i = 1; i <= rootProcs.length; i++) {
253 rootProcs[i-1] = new TestProcedure(i, 0);
254 procStore.insert(rootProcs[i-1], null);
255 rootProcs[i-1].addStackId(0);
256 procStore.update(rootProcs[i-1]);
257 }
258
259 procStore.rollWriterForTesting();
260 for (int i = 1; i <= rootProcs.length; i++) {
261 TestProcedure b = new TestProcedure(rootProcs.length + i, i);
262 rootProcs[i-1].addStackId(1);
263 procStore.insert(rootProcs[i-1], new Procedure[] { b });
264 }
265
266 procStore.rollWriterForTesting();
267 for (int i = 1; i <= rootProcs.length; i++) {
268 procStore.update(new TestProcedure(rootProcs.length + i, i));
269 }
270
271
272 procStore.stop(false);
273
274
275
276 FileStatus[] logs = fs.listStatus(logDir);
277 assertEquals(Arrays.toString(logs), 2, logs.length);
278 Arrays.sort(logs, new Comparator<FileStatus>() {
279 @Override
280 public int compare(FileStatus o1, FileStatus o2) {
281 return o1.getPath().getName().compareTo(o2.getPath().getName());
282 }
283 });
284
285 LoadCounter loader = new LoadCounter();
286 storeRestart(loader);
287 assertEquals(rootProcs.length * 2, loader.getLoadedCount());
288 assertEquals(0, loader.getCorruptedCount());
289
290
291 fs.delete(logs[0].getPath(), false);
292 loader.reset();
293 storeRestart(loader);
294 assertEquals(0, loader.getLoadedCount());
295 assertEquals(rootProcs.length, loader.getCorruptedCount());
296 for (Procedure proc: loader.getCorrupted()) {
297 assertTrue(proc.toString(), proc.getParentProcId() <= rootProcs.length);
298 assertTrue(proc.toString(),
299 proc.getProcId() > rootProcs.length &&
300 proc.getProcId() <= (rootProcs.length * 2));
301 }
302 }
303
304 @Test(timeout=60000)
305 public void testWalReplayOrder_AB_A() throws Exception {
306
307
308
309 TestProcedure a = new TestProcedure(1, 0);
310 TestProcedure b = new TestProcedure(2, 1);
311
312 procStore.insert(a, null);
313 a.addStackId(0);
314 procStore.update(a);
315
316 procStore.insert(a, new Procedure[] { b });
317 b.addStackId(1);
318 procStore.update(b);
319
320 procStore.rollWriterForTesting();
321
322 a.addStackId(2);
323 procStore.update(a);
324
325 storeRestart(new ProcedureStore.ProcedureLoader() {
326 @Override
327 public void setMaxProcId(long maxProcId) {
328 assertEquals(2, maxProcId);
329 }
330
331 @Override
332 public void load(ProcedureIterator procIter) throws IOException {
333 assertTrue(procIter.hasNext());
334 assertEquals(1, procIter.nextAsProcedureInfo().getProcId());
335 assertTrue(procIter.hasNext());
336 assertEquals(2, procIter.nextAsProcedureInfo().getProcId());
337 assertFalse(procIter.hasNext());
338 }
339
340 @Override
341 public void handleCorrupted(ProcedureIterator procIter) throws IOException {
342 assertFalse(procIter.hasNext());
343 }
344 });
345 }
346
347 @Test(timeout=60000)
348 public void testWalReplayOrder_ABC_BAD() throws Exception {
349
350
351
352 TestProcedure a = new TestProcedure(1, 0);
353 TestProcedure b = new TestProcedure(2, 1);
354 TestProcedure c = new TestProcedure(3, 2);
355 TestProcedure d = new TestProcedure(4, 0);
356
357 procStore.insert(a, null);
358 a.addStackId(0);
359 procStore.update(a);
360
361 procStore.insert(a, new Procedure[] { b });
362 b.addStackId(1);
363 procStore.update(b);
364
365 procStore.insert(b, new Procedure[] { c });
366 b.addStackId(2);
367 procStore.update(b);
368
369 procStore.rollWriterForTesting();
370
371 b.addStackId(3);
372 procStore.update(b);
373
374 a.addStackId(4);
375 procStore.update(a);
376
377 procStore.insert(d, null);
378 d.addStackId(0);
379 procStore.update(d);
380
381 storeRestart(new ProcedureStore.ProcedureLoader() {
382 @Override
383 public void setMaxProcId(long maxProcId) {
384 assertEquals(4, maxProcId);
385 }
386
387 @Override
388 public void load(ProcedureIterator procIter) throws IOException {
389 assertTrue(procIter.hasNext());
390 assertEquals(4, procIter.nextAsProcedureInfo().getProcId());
391
392
393
394 assertTrue(procIter.hasNext());
395 assertEquals(1, procIter.nextAsProcedureInfo().getProcId());
396 assertTrue(procIter.hasNext());
397 assertEquals(2, procIter.nextAsProcedureInfo().getProcId());
398 assertTrue(procIter.hasNext());
399 assertEquals(3, procIter.nextAsProcedureInfo().getProcId());
400 assertFalse(procIter.hasNext());
401 }
402
403 @Override
404 public void handleCorrupted(ProcedureIterator procIter) throws IOException {
405 assertFalse(procIter.hasNext());
406 }
407 });
408 }
409
410 @Test
411 public void testRollAndRemove() throws IOException {
412
413 Procedure proc1 = new TestSequentialProcedure();
414 procStore.insert(proc1, null);
415
416 Procedure proc2 = new TestSequentialProcedure();
417 procStore.insert(proc2, null);
418
419
420 procStore.rollWriterForTesting();
421 assertEquals(2, procStore.getActiveLogs().size());
422
423
424
425 procStore.update(proc1);
426 procStore.update(proc2);
427 assertEquals(1, procStore.getActiveLogs().size());
428
429
430 procStore.rollWriterForTesting();
431 assertEquals(2, procStore.getActiveLogs().size());
432
433
434
435 procStore.delete(proc1.getProcId());
436 procStore.delete(proc2.getProcId());
437 assertEquals(1, procStore.getActiveLogs().size());
438 }
439
440 @Test
441 public void testFileNotFoundDuringLeaseRecovery() throws IOException {
442 TestProcedure[] procs = new TestProcedure[3];
443 for (int i = 0; i < procs.length; ++i) {
444 procs[i] = new TestProcedure(i + 1, 0);
445 procStore.insert(procs[i], null);
446 }
447 procStore.rollWriterForTesting();
448 for (int i = 0; i < procs.length; ++i) {
449 procStore.update(procs[i]);
450 procStore.rollWriterForTesting();
451 }
452 procStore.stop(false);
453
454 FileStatus[] status = fs.listStatus(logDir);
455 assertEquals(procs.length + 2, status.length);
456
457
458 procStore = new WALProcedureStore(htu.getConfiguration(), logDir,
459 new WALProcedureStore.LeaseRecovery() {
460 private int count = 0;
461
462 @Override
463 public void recoverFileLease(FileSystem fs, Path path) throws IOException {
464 if (++count <= 2) {
465 fs.delete(path, false);
466 LOG.debug("Simulate FileNotFound at count=" + count + " for " + path);
467 throw new FileNotFoundException("test file not found " + path);
468 }
469 LOG.debug("Simulate recoverFileLease() at count=" + count + " for " + path);
470 }
471 });
472
473 final LoadCounter loader = new LoadCounter();
474 procStore.start(PROCEDURE_STORE_SLOTS);
475 procStore.recoverLease();
476 procStore.load(loader);
477 assertEquals(procs.length, loader.getMaxProcId());
478 assertEquals(procs.length - 1, loader.getRunnableCount());
479 assertEquals(0, loader.getCompletedCount());
480 assertEquals(0, loader.getCorruptedCount());
481 }
482
483 @Test
484 public void testLoadChildren() throws Exception {
485 TestProcedure a = new TestProcedure(1, 0);
486 TestProcedure b = new TestProcedure(2, 1);
487 TestProcedure c = new TestProcedure(3, 1);
488
489
490 procStore.insert(a, null);
491
492
493 a.addStackId(0);
494 procStore.update(a);
495
496
497 a.addStackId(1);
498 procStore.insert(a, new Procedure[] { b, c });
499
500
501 b.addStackId(2);
502 procStore.update(b);
503
504
505 c.addStackId(3);
506 procStore.update(c);
507
508
509 b.addStackId(4);
510 procStore.update(b);
511
512
513 a.addStackId(5);
514 a.setFinishedState();
515 procStore.delete(a, new long[] { b.getProcId(), c.getProcId() });
516 restartAndAssert(3, 0, 1, 0);
517 }
518
519 private void restartAndAssert(long maxProcId, long runnableCount,
520 int completedCount, int corruptedCount) throws Exception {
521 ProcedureTestingUtility.storeRestartAndAssert(procStore, maxProcId,
522 runnableCount, completedCount, corruptedCount);
523 }
524
525 private void corruptLog(final FileStatus logFile, final long dropBytes)
526 throws IOException {
527 assertTrue(logFile.getLen() > dropBytes);
528 LOG.debug("corrupt log " + logFile.getPath() +
529 " size=" + logFile.getLen() + " drop=" + dropBytes);
530 Path tmpPath = new Path(testDir, "corrupted.log");
531 InputStream in = fs.open(logFile.getPath());
532 OutputStream out = fs.create(tmpPath);
533 IOUtils.copyBytes(in, out, logFile.getLen() - dropBytes, true);
534 if (!fs.rename(tmpPath, logFile.getPath())) {
535 throw new IOException("Unable to rename");
536 }
537 }
538
539 private void verifyProcIdsOnRestart(final Set<Long> procIds) throws Exception {
540 LOG.debug("expected: " + procIds);
541 LoadCounter loader = new LoadCounter();
542 storeRestart(loader);
543 assertEquals(procIds.size(), loader.getLoadedCount());
544 assertEquals(0, loader.getCorruptedCount());
545 }
546
547 private void assertEmptyLogDir() {
548 try {
549 FileStatus[] status = fs.listStatus(logDir);
550 assertTrue("expected empty state-log dir", status == null || status.length == 0);
551 } catch (FileNotFoundException e) {
552 fail("expected the state-log dir to be present: " + logDir);
553 } catch (IOException e) {
554 fail("got en exception on state-log dir list: " + e.getMessage());
555 }
556 }
557
558 public static class TestSequentialProcedure extends SequentialProcedure<Void> {
559 private static long seqid = 0;
560
561 public TestSequentialProcedure() {
562 setProcId(++seqid);
563 }
564
565 @Override
566 protected Procedure[] execute(Void env) { return null; }
567
568 @Override
569 protected void rollback(Void env) { }
570
571 @Override
572 protected boolean abort(Void env) { return false; }
573
574 @Override
575 protected void serializeStateData(final OutputStream stream) throws IOException {
576 long procId = getProcId();
577 if (procId % 2 == 0) {
578 stream.write(Bytes.toBytes(procId));
579 }
580 }
581
582 @Override
583 protected void deserializeStateData(InputStream stream) throws IOException {
584 long procId = getProcId();
585 if (procId % 2 == 0) {
586 byte[] bProcId = new byte[8];
587 assertEquals(8, stream.read(bProcId));
588 assertEquals(procId, Bytes.toLong(bProcId));
589 } else {
590 assertEquals(0, stream.available());
591 }
592 }
593 }
594 }