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.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
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 testCorruptedTrailer() throws Exception {
155
156 for (int i = 0; i < 100; ++i) {
157 procStore.insert(new TestSequentialProcedure(), null);
158 }
159
160
161 procStore.stop(false);
162
163
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
175 for (int i = 0; i < 100; ++i) {
176 procStore.insert(new TestSequentialProcedure(), null);
177 }
178
179
180 procStore.stop(false);
181
182
183
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
225 procStore.insert(proc, null);
226
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
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
252 Procedure proc1 = new TestSequentialProcedure();
253 procStore.insert(proc1, null);
254
255 Procedure proc2 = new TestSequentialProcedure();
256 procStore.insert(proc2, null);
257
258
259 procStore.rollWriterForTesting();
260 assertEquals(2, procStore.getActiveLogs().size());
261
262
263
264 procStore.update(proc1);
265 procStore.update(proc2);
266 assertEquals(1, procStore.getActiveLogs().size());
267
268
269 procStore.rollWriterForTesting();
270 assertEquals(2, procStore.getActiveLogs().size());
271
272
273
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 }