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.Iterator;
26  import java.util.HashSet;
27  import java.util.Set;
28  import java.util.Random;
29  import java.util.concurrent.atomic.AtomicLong;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.fs.FileSystem;
34  import org.apache.hadoop.fs.FileStatus;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
37  import org.apache.hadoop.hbase.procedure2.Procedure;
38  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
39  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility.TestProcedure;
40  import org.apache.hadoop.hbase.procedure2.SequentialProcedure;
41  import org.apache.hadoop.hbase.testclassification.SmallTests;
42  import org.apache.hadoop.hbase.util.Bytes;
43  import org.apache.hadoop.io.IOUtils;
44  
45  import org.junit.After;
46  import org.junit.Before;
47  import org.junit.Assert;
48  import org.junit.Test;
49  import org.junit.experimental.categories.Category;
50  
51  import static org.junit.Assert.assertEquals;
52  import static org.junit.Assert.assertFalse;
53  import static org.junit.Assert.assertTrue;
54  import static org.junit.Assert.fail;
55  
56  @Category(SmallTests.class)
57  public class TestWALProcedureStore {
58    private static final Log LOG = LogFactory.getLog(TestWALProcedureStore.class);
59  
60    private static final int PROCEDURE_STORE_SLOTS = 1;
61    private static final Procedure NULL_PROC = null;
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(), fs, logDir);
79      procStore.start(PROCEDURE_STORE_SLOTS);
80      procStore.recoverLease();
81      procStore.load();
82    }
83  
84    @After
85    public void tearDown() throws IOException {
86      procStore.stop(false);
87      fs.delete(logDir, true);
88    }
89  
90    private Iterator<Procedure> storeRestart() throws Exception {
91      procStore.stop(false);
92      procStore.start(PROCEDURE_STORE_SLOTS);
93      procStore.recoverLease();
94      return procStore.load();
95    }
96  
97    @Test
98    public void testEmptyRoll() throws Exception {
99      for (int i = 0; i < 10; ++i) {
100       procStore.periodicRollForTesting();
101     }
102     FileStatus[] status = fs.listStatus(logDir);
103     assertEquals(1, status.length);
104   }
105 
106   @Test
107   public void testEmptyLogLoad() throws Exception {
108     Iterator<Procedure> loader = storeRestart();
109     assertEquals(0, countProcedures(loader));
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 testCorruptedTrailer() throws Exception {
155     // Insert something
156     for (int i = 0; i < 100; ++i) {
157       procStore.insert(new TestSequentialProcedure(), null);
158     }
159 
160     // Stop the store
161     procStore.stop(false);
162 
163     // Remove 4 byte from the trailer
164     FileStatus[] logs = fs.listStatus(logDir);
165     assertEquals(1, logs.length);
166     corruptLog(logs[0], 4);
167 
168     int count = countProcedures(storeRestart());
169     assertEquals(100, count);
170   }
171 
172   @Test
173   public void testCorruptedEntries() throws Exception {
174     // Insert something
175     for (int i = 0; i < 100; ++i) {
176       procStore.insert(new TestSequentialProcedure(), null);
177     }
178 
179     // Stop the store
180     procStore.stop(false);
181 
182     // Remove some byte from the log
183     // (enough to cut the trailer and corrupt some entries)
184     FileStatus[] logs = fs.listStatus(logDir);
185     assertEquals(1, logs.length);
186     corruptLog(logs[0], 1823);
187 
188     int count = countProcedures(storeRestart());
189     assertTrue(procStore.getCorruptedLogs() != null);
190     assertEquals(1, procStore.getCorruptedLogs().size());
191     assertEquals(85, count);
192   }
193 
194   @Test
195   public void testInsertUpdateDelete() throws Exception {
196     final int NTHREAD = 2;
197 
198     procStore.stop(false);
199     fs.delete(logDir, true);
200 
201     org.apache.hadoop.conf.Configuration conf =
202       new org.apache.hadoop.conf.Configuration(htu.getConfiguration());
203     conf.setBoolean("hbase.procedure.store.wal.use.hsync", false);
204     conf.setInt("hbase.procedure.store.wal.periodic.roll.msec", 10000);
205     conf.setInt("hbase.procedure.store.wal.roll.threshold", 128 * 1024);
206 
207     fs.mkdirs(logDir);
208     procStore = ProcedureTestingUtility.createWalStore(conf, fs, logDir);
209     procStore.start(NTHREAD);
210     procStore.recoverLease();
211     assertEquals(0, countProcedures(procStore.load()));
212 
213     final long LAST_PROC_ID = 9999;
214     final Thread[] thread = new Thread[NTHREAD];
215     final AtomicLong procCounter = new AtomicLong((long)Math.round(Math.random() * 100));
216     for (int i = 0; i < thread.length; ++i) {
217       thread[i] = new Thread() {
218         @Override
219         public void run() {
220           Random rand = new Random();
221           TestProcedure proc;
222           do {
223             proc = new TestProcedure(procCounter.addAndGet(1));
224             // Insert
225             procStore.insert(proc, null);
226             // Update
227             for (int i = 0, nupdates = rand.nextInt(10); i <= nupdates; ++i) {
228               try { Thread.sleep(0, rand.nextInt(15)); } catch (InterruptedException e) {}
229               procStore.update(proc);
230             }
231             // Delete
232             procStore.delete(proc.getProcId());
233           } while (proc.getProcId() < LAST_PROC_ID);
234         }
235       };
236       thread[i].start();
237     }
238 
239     for (int i = 0; i < thread.length; ++i) {
240       thread[i].join();
241     }
242 
243     procStore.getStoreTracker().dump();
244     assertTrue(procCounter.get() >= LAST_PROC_ID);
245     assertTrue(procStore.getStoreTracker().isEmpty());
246     assertEquals(1, procStore.getActiveLogs().size());
247   }
248 
249   @Test
250   public void testRollAndRemove() throws IOException {
251     // Insert something in the log
252     Procedure proc1 = new TestSequentialProcedure();
253     procStore.insert(proc1, null);
254 
255     Procedure proc2 = new TestSequentialProcedure();
256     procStore.insert(proc2, null);
257 
258     // roll the log, now we have 2
259     procStore.rollWriterForTesting();
260     assertEquals(2, procStore.getActiveLogs().size());
261 
262     // everything will be up to date in the second log
263     // so we can remove the first one
264     procStore.update(proc1);
265     procStore.update(proc2);
266     assertEquals(1, procStore.getActiveLogs().size());
267 
268     // roll the log, now we have 2
269     procStore.rollWriterForTesting();
270     assertEquals(2, procStore.getActiveLogs().size());
271 
272     // remove everything active
273     // so we can remove all the logs
274     procStore.delete(proc1.getProcId());
275     procStore.delete(proc2.getProcId());
276     assertEquals(1, procStore.getActiveLogs().size());
277   }
278 
279   private void corruptLog(final FileStatus logFile, final long dropBytes)
280       throws IOException {
281     assertTrue(logFile.getLen() > dropBytes);
282     LOG.debug("corrupt log " + logFile.getPath() +
283               " size=" + logFile.getLen() + " drop=" + dropBytes);
284     Path tmpPath = new Path(testDir, "corrupted.log");
285     InputStream in = fs.open(logFile.getPath());
286     OutputStream out =  fs.create(tmpPath);
287     IOUtils.copyBytes(in, out, logFile.getLen() - dropBytes, true);
288     if (!fs.rename(tmpPath, logFile.getPath())) {
289       throw new IOException("Unable to rename");
290     }
291   }
292 
293   private void verifyProcIdsOnRestart(final Set<Long> procIds) throws Exception {
294     int count = 0;
295     Iterator<Procedure> loader = storeRestart();
296     while (loader.hasNext()) {
297       Procedure proc = loader.next();
298       LOG.debug("loading procId=" + proc.getProcId());
299       assertTrue("procId=" + proc.getProcId() + " unexpected", procIds.contains(proc.getProcId()));
300       count++;
301     }
302     assertEquals(procIds.size(), count);
303   }
304 
305   private void assertIsEmpty(Iterator<Procedure> iterator) {
306     assertEquals(0, countProcedures(iterator));
307   }
308 
309   private int countProcedures(Iterator<Procedure> iterator) {
310     int count = 0;
311     while (iterator != null && iterator.hasNext()) {
312       Procedure proc = iterator.next();
313       LOG.trace("loading procId=" + proc.getProcId());
314       count++;
315     }
316     return count;
317   }
318 
319   private void assertEmptyLogDir() {
320     try {
321       FileStatus[] status = fs.listStatus(logDir);
322       assertTrue("expected empty state-log dir", status == null || status.length == 0);
323     } catch (FileNotFoundException e) {
324       fail("expected the state-log dir to be present: " + logDir);
325     } catch (IOException e) {
326       fail("got en exception on state-log dir list: " + e.getMessage());
327     }
328   }
329 
330   public static class TestSequentialProcedure extends SequentialProcedure<Void> {
331     private static long seqid = 0;
332 
333     public TestSequentialProcedure() {
334       setProcId(++seqid);
335     }
336 
337     @Override
338     protected Procedure[] execute(Void env) { return null; }
339 
340     @Override
341     protected void rollback(Void env) { }
342 
343     @Override
344     protected boolean abort(Void env) { return false; }
345 
346     @Override
347     protected void serializeStateData(final OutputStream stream) throws IOException {
348       long procId = getProcId();
349       if (procId % 2 == 0) {
350         stream.write(Bytes.toBytes(procId));
351       }
352     }
353 
354     @Override
355     protected void deserializeStateData(InputStream stream) throws IOException {
356       long procId = getProcId();
357       if (procId % 2 == 0) {
358         byte[] bProcId = new byte[8];
359         assertEquals(8, stream.read(bProcId));
360         assertEquals(procId, Bytes.toLong(bProcId));
361       } else {
362         assertEquals(0, stream.available());
363       }
364     }
365   }
366 }