View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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     // Insert something in the log
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     // Verify that everything is there
132     verifyProcIdsOnRestart(procIds);
133 
134     // Update and delete something
135     procStore.update(proc1);
136     procStore.update(child2[1]);
137     procStore.delete(child2[1].getProcId());
138     procIds.remove(child2[1].getProcId());
139 
140     // Verify that everything is there
141     verifyProcIdsOnRestart(procIds);
142 
143     // Remove 4 byte from the trailers
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     // log-0001: proc 0, 1 and 2 are inserted
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     // log-0002: proc 1 deleted
165     procStore.delete(proc1.getProcId());
166     procStore.rollWriterForTesting();
167 
168     // log-0003: proc 2 is update
169     procStore.update(proc2);
170     procStore.rollWriterForTesting();
171 
172     // log-0004: proc 2 deleted
173     procStore.delete(proc2.getProcId());
174 
175     // stop the store and remove the trailer
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     // Test Load 1
184     LoadCounter loader = new LoadCounter();
185     storeRestart(loader);
186     assertEquals(1, loader.getLoadedCount());
187     assertEquals(0, loader.getCorruptedCount());
188 
189     // Test Load 2
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     // remove proc-0
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     // Insert something
206     for (int i = 0; i < 100; ++i) {
207       procStore.insert(new TestSequentialProcedure(), null);
208     }
209 
210     // Stop the store
211     procStore.stop(false);
212 
213     // Remove 4 byte from the trailer
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     // Insert something
227     for (int i = 0; i < 100; ++i) {
228       procStore.insert(new TestSequentialProcedure(), null);
229     }
230 
231     // Stop the store
232     procStore.stop(false);
233 
234     // Remove some byte from the log
235     // (enough to cut the trailer and corrupt some entries)
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     // Insert root-procedures
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     // insert root-child txn
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     // insert child updates
266     procStore.rollWriterForTesting();
267     for (int i = 1; i <= rootProcs.length; i++) {
268       procStore.update(new TestProcedure(rootProcs.length + i, i));
269     }
270 
271     // Stop the store
272     procStore.stop(false);
273 
274     // the first log was removed,
275     // we have insert-txn and updates in the others so everything is fine
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     // Remove the second log, we have lost all the root/parent references
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      * | A B | -> | A |
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      * | A B C | -> | B A D |
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         // TODO: This will be multiple call once we do fast-start
392         //assertFalse(procIter.hasNext());
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     // Insert something in the log
413     Procedure proc1 = new TestSequentialProcedure();
414     procStore.insert(proc1, null);
415 
416     Procedure proc2 = new TestSequentialProcedure();
417     procStore.insert(proc2, null);
418 
419     // roll the log, now we have 2
420     procStore.rollWriterForTesting();
421     assertEquals(2, procStore.getActiveLogs().size());
422 
423     // everything will be up to date in the second log
424     // so we can remove the first one
425     procStore.update(proc1);
426     procStore.update(proc2);
427     assertEquals(1, procStore.getActiveLogs().size());
428 
429     // roll the log, now we have 2
430     procStore.rollWriterForTesting();
431     assertEquals(2, procStore.getActiveLogs().size());
432 
433     // remove everything active
434     // so we can remove all the logs
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     // simulate another active master removing the wals
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     // INIT
490     procStore.insert(a, null);
491 
492     // Run A first step
493     a.addStackId(0);
494     procStore.update(a);
495 
496     // Run A second step
497     a.addStackId(1);
498     procStore.insert(a, new Procedure[] { b, c });
499 
500     // Run B first step
501     b.addStackId(2);
502     procStore.update(b);
503 
504     // Run C first and last step
505     c.addStackId(3);
506     procStore.update(c);
507 
508     // Run B second setp
509     b.addStackId(4);
510     procStore.update(b);
511 
512     // back to A
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 }