View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver.wal;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.Comparator;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.UUID;
34  import java.util.concurrent.atomic.AtomicLong;
35  
36  import org.apache.commons.lang.mutable.MutableBoolean;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.conf.Configuration;
40  import org.apache.hadoop.fs.FileStatus;
41  import org.apache.hadoop.fs.FileSystem;
42  import org.apache.hadoop.fs.Path;
43  import org.apache.hadoop.hbase.CellScanner;
44  import org.apache.hadoop.hbase.Coprocessor;
45  import org.apache.hadoop.hbase.HBaseTestingUtility;
46  import org.apache.hadoop.hbase.HBaseConfiguration;
47  import org.apache.hadoop.hbase.HColumnDescriptor;
48  import org.apache.hadoop.hbase.HConstants;
49  import org.apache.hadoop.hbase.HRegionInfo;
50  import org.apache.hadoop.hbase.HTableDescriptor;
51  import org.apache.hadoop.hbase.KeyValue;
52  import org.apache.hadoop.hbase.testclassification.MediumTests;
53  import org.apache.hadoop.hbase.TableName;
54  import org.apache.hadoop.hbase.client.Get;
55  import org.apache.hadoop.hbase.client.Put;
56  import org.apache.hadoop.hbase.client.Result;
57  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
58  import org.apache.hadoop.hbase.coprocessor.SampleRegionWALObserver;
59  import org.apache.hadoop.hbase.regionserver.HRegion;
60  import org.apache.hadoop.hbase.util.Bytes;
61  import org.apache.hadoop.hbase.util.EnvironmentEdge;
62  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
63  import org.apache.hadoop.hbase.util.FSUtils;
64  import org.apache.hadoop.hbase.util.Threads;
65  import org.apache.hadoop.hbase.wal.DefaultWALProvider;
66  import org.apache.hadoop.hbase.wal.WAL;
67  import org.apache.hadoop.hbase.wal.WALKey;
68  import org.junit.After;
69  import org.junit.AfterClass;
70  import org.junit.Before;
71  import org.junit.BeforeClass;
72  import org.junit.Rule;
73  import org.junit.Test;
74  import org.junit.experimental.categories.Category;
75  import org.junit.rules.TestName;
76  
77  /**
78   * Provides FSHLog test cases.
79   */
80  @Category(MediumTests.class)
81  public class TestFSHLog {
82    protected static final Log LOG = LogFactory.getLog(TestFSHLog.class);
83  
84    protected static Configuration conf;
85    protected static FileSystem fs;
86    protected static Path dir;
87    protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
88  
89    @Rule
90    public final TestName currentTest = new TestName();
91  
92    @Before
93    public void setUp() throws Exception {
94      FileStatus[] entries = fs.listStatus(new Path("/"));
95      for (FileStatus dir : entries) {
96        fs.delete(dir.getPath(), true);
97      }
98      final Path hbaseDir = TEST_UTIL.createRootDir();
99      dir = new Path(hbaseDir, currentTest.getMethodName());
100   }
101 
102   @After
103   public void tearDown() throws Exception {
104   }
105 
106   @BeforeClass
107   public static void setUpBeforeClass() throws Exception {
108     // Make block sizes small.
109     TEST_UTIL.getConfiguration().setInt("dfs.blocksize", 1024 * 1024);
110     // quicker heartbeat interval for faster DN death notification
111     TEST_UTIL.getConfiguration().setInt("dfs.namenode.heartbeat.recheck-interval", 5000);
112     TEST_UTIL.getConfiguration().setInt("dfs.heartbeat.interval", 1);
113     TEST_UTIL.getConfiguration().setInt("dfs.client.socket-timeout", 5000);
114 
115     // faster failover with cluster.shutdown();fs.close() idiom
116     TEST_UTIL.getConfiguration()
117         .setInt("hbase.ipc.client.connect.max.retries", 1);
118     TEST_UTIL.getConfiguration().setInt(
119         "dfs.client.block.recovery.retries", 1);
120     TEST_UTIL.getConfiguration().setInt(
121       "hbase.ipc.client.connection.maxidletime", 500);
122     TEST_UTIL.getConfiguration().set(CoprocessorHost.WAL_COPROCESSOR_CONF_KEY,
123         SampleRegionWALObserver.class.getName());
124     TEST_UTIL.startMiniDFSCluster(3);
125 
126     conf = TEST_UTIL.getConfiguration();
127     fs = TEST_UTIL.getDFSCluster().getFileSystem();
128   }
129 
130   @AfterClass
131   public static void tearDownAfterClass() throws Exception {
132     TEST_UTIL.shutdownMiniCluster();
133   }
134 
135   /**
136    * A loaded WAL coprocessor won't break existing WAL test cases.
137    */
138   @Test
139   public void testWALCoprocessorLoaded() throws Exception {
140     // test to see whether the coprocessor is loaded or not.
141     FSHLog log = null;
142     try {
143       log = new FSHLog(fs, FSUtils.getRootDir(conf), dir.toString(),
144           HConstants.HREGION_OLDLOGDIR_NAME, conf, null, true, null, null);
145       WALCoprocessorHost host = log.getCoprocessorHost();
146       Coprocessor c = host.findCoprocessor(SampleRegionWALObserver.class.getName());
147       assertNotNull(c);
148     } finally {
149       if (log != null) {
150         log.close();
151       }
152     }
153   }
154 
155   protected void addEdits(WAL log, HRegionInfo hri, TableName tableName,
156                         int times, AtomicLong sequenceId) throws IOException {
157     HTableDescriptor htd = new HTableDescriptor();
158     htd.addFamily(new HColumnDescriptor("row"));
159 
160     final byte [] row = Bytes.toBytes("row");
161     for (int i = 0; i < times; i++) {
162       long timestamp = System.currentTimeMillis();
163       WALEdit cols = new WALEdit();
164       cols.add(new KeyValue(row, row, row, timestamp, row));
165       log.append(htd, hri, new WALKey(hri.getEncodedNameAsBytes(), tableName, timestamp), cols,
166           sequenceId, true, null);
167     }
168     log.sync();
169   }
170 
171   /**
172    * helper method to simulate region flush for a WAL.
173    * @param wal
174    * @param regionEncodedName
175    */
176   protected void flushRegion(WAL wal, byte[] regionEncodedName) {
177     wal.startCacheFlush(regionEncodedName);
178     wal.completeCacheFlush(regionEncodedName);
179   }
180 
181   /**
182    * tests the log comparator. Ensure that we are not mixing meta logs with non-meta logs (throws
183    * exception if we do). Comparison is based on the timestamp present in the wal name.
184    * @throws Exception
185    */
186   @Test 
187   public void testWALComparator() throws Exception {
188     FSHLog wal1 = null;
189     FSHLog walMeta = null;
190     try {
191       wal1 = new FSHLog(fs, FSUtils.getRootDir(conf), dir.toString(),
192           HConstants.HREGION_OLDLOGDIR_NAME, conf, null, true, null, null);
193       LOG.debug("Log obtained is: " + wal1);
194       Comparator<Path> comp = wal1.LOG_NAME_COMPARATOR;
195       Path p1 = wal1.computeFilename(11);
196       Path p2 = wal1.computeFilename(12);
197       // comparing with itself returns 0
198       assertTrue(comp.compare(p1, p1) == 0);
199       // comparing with different filenum.
200       assertTrue(comp.compare(p1, p2) < 0);
201       walMeta = new FSHLog(fs, FSUtils.getRootDir(conf), dir.toString(),
202           HConstants.HREGION_OLDLOGDIR_NAME, conf, null, true, null,
203           DefaultWALProvider.META_WAL_PROVIDER_ID);
204       Comparator<Path> compMeta = walMeta.LOG_NAME_COMPARATOR;
205 
206       Path p1WithMeta = walMeta.computeFilename(11);
207       Path p2WithMeta = walMeta.computeFilename(12);
208       assertTrue(compMeta.compare(p1WithMeta, p1WithMeta) == 0);
209       assertTrue(compMeta.compare(p1WithMeta, p2WithMeta) < 0);
210       // mixing meta and non-meta logs gives error
211       boolean ex = false;
212       try {
213         comp.compare(p1WithMeta, p2);
214       } catch (IllegalArgumentException e) {
215         ex = true;
216       }
217       assertTrue("Comparator doesn't complain while checking meta log files", ex);
218       boolean exMeta = false;
219       try {
220         compMeta.compare(p1WithMeta, p2);
221       } catch (IllegalArgumentException e) {
222         exMeta = true;
223       }
224       assertTrue("Meta comparator doesn't complain while checking log files", exMeta);
225     } finally {
226       if (wal1 != null) {
227         wal1.close();
228       }
229       if (walMeta != null) {
230         walMeta.close();
231       }
232     }
233   }
234 
235   /**
236    * On rolling a wal after reaching the threshold, {@link WAL#rollWriter()} returns the
237    * list of regions which should be flushed in order to archive the oldest wal file.
238    * <p>
239    * This method tests this behavior by inserting edits and rolling the wal enough times to reach
240    * the max number of logs threshold. It checks whether we get the "right regions" for flush on
241    * rolling the wal.
242    * @throws Exception
243    */
244   @Test 
245   public void testFindMemStoresEligibleForFlush() throws Exception {
246     LOG.debug("testFindMemStoresEligibleForFlush");
247     Configuration conf1 = HBaseConfiguration.create(conf);
248     conf1.setInt("hbase.regionserver.maxlogs", 1);
249     FSHLog wal = new FSHLog(fs, FSUtils.getRootDir(conf1), dir.toString(),
250         HConstants.HREGION_OLDLOGDIR_NAME, conf1, null, true, null, null);
251     TableName t1 = TableName.valueOf("t1");
252     TableName t2 = TableName.valueOf("t2");
253     HRegionInfo hri1 = new HRegionInfo(t1, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
254     HRegionInfo hri2 = new HRegionInfo(t2, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
255     // variables to mock region sequenceIds
256     final AtomicLong sequenceId1 = new AtomicLong(1);
257     final AtomicLong sequenceId2 = new AtomicLong(1);
258     // add edits and roll the wal
259     try {
260       addEdits(wal, hri1, t1, 2, sequenceId1);
261       wal.rollWriter();
262       // add some more edits and roll the wal. This would reach the log number threshold
263       addEdits(wal, hri1, t1, 2, sequenceId1);
264       wal.rollWriter();
265       // with above rollWriter call, the max logs limit is reached.
266       assertTrue(wal.getNumRolledLogFiles() == 2);
267 
268       // get the regions to flush; since there is only one region in the oldest wal, it should
269       // return only one region.
270       byte[][] regionsToFlush = wal.findRegionsToForceFlush();
271       assertEquals(1, regionsToFlush.length);
272       assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]);
273       // insert edits in second region
274       addEdits(wal, hri2, t2, 2, sequenceId2);
275       // get the regions to flush, it should still read region1.
276       regionsToFlush = wal.findRegionsToForceFlush();
277       assertEquals(regionsToFlush.length, 1);
278       assertEquals(hri1.getEncodedNameAsBytes(), regionsToFlush[0]);
279       // flush region 1, and roll the wal file. Only last wal which has entries for region1 should
280       // remain.
281       flushRegion(wal, hri1.getEncodedNameAsBytes());
282       wal.rollWriter();
283       // only one wal should remain now (that is for the second region).
284       assertEquals(1, wal.getNumRolledLogFiles());
285       // flush the second region
286       flushRegion(wal, hri2.getEncodedNameAsBytes());
287       wal.rollWriter(true);
288       // no wal should remain now.
289       assertEquals(0, wal.getNumRolledLogFiles());
290       // add edits both to region 1 and region 2, and roll.
291       addEdits(wal, hri1, t1, 2, sequenceId1);
292       addEdits(wal, hri2, t2, 2, sequenceId2);
293       wal.rollWriter();
294       // add edits and roll the writer, to reach the max logs limit.
295       assertEquals(1, wal.getNumRolledLogFiles());
296       addEdits(wal, hri1, t1, 2, sequenceId1);
297       wal.rollWriter();
298       // it should return two regions to flush, as the oldest wal file has entries
299       // for both regions.
300       regionsToFlush = wal.findRegionsToForceFlush();
301       assertEquals(2, regionsToFlush.length);
302       // flush both regions
303       flushRegion(wal, hri1.getEncodedNameAsBytes());
304       flushRegion(wal, hri2.getEncodedNameAsBytes());
305       wal.rollWriter(true);
306       assertEquals(0, wal.getNumRolledLogFiles());
307       // Add an edit to region1, and roll the wal.
308       addEdits(wal, hri1, t1, 2, sequenceId1);
309       // tests partial flush: roll on a partial flush, and ensure that wal is not archived.
310       wal.startCacheFlush(hri1.getEncodedNameAsBytes());
311       wal.rollWriter();
312       wal.completeCacheFlush(hri1.getEncodedNameAsBytes());
313       assertEquals(1, wal.getNumRolledLogFiles());
314     } finally {
315       if (wal != null) {
316         wal.close();
317       }
318     }
319   }
320 
321   /**
322    * Simulates WAL append ops for a region and tests
323    * {@link FSHLog#areAllRegionsFlushed(Map, Map, Map)} API.
324    * It compares the region sequenceIds with oldestFlushing and oldestUnFlushed entries.
325    * If a region's entries are larger than min of (oldestFlushing, oldestUnFlushed), then the
326    * region should be flushed before archiving this WAL.
327   */
328   @Test
329   public void testAllRegionsFlushed() {
330     LOG.debug("testAllRegionsFlushed");
331     Map<byte[], Long> oldestFlushingSeqNo = new HashMap<byte[], Long>();
332     Map<byte[], Long> oldestUnFlushedSeqNo = new HashMap<byte[], Long>();
333     Map<byte[], Long> seqNo = new HashMap<byte[], Long>();
334     // create a table
335     TableName t1 = TableName.valueOf("t1");
336     // create a region
337     HRegionInfo hri1 = new HRegionInfo(t1, HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW);
338     // variables to mock region sequenceIds
339     final AtomicLong sequenceId1 = new AtomicLong(1);
340     // test empty map
341     assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
342     // add entries in the region
343     seqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.incrementAndGet());
344     oldestUnFlushedSeqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.get());
345     // should say region1 is not flushed.
346     assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
347     // test with entries in oldestFlushing map.
348     oldestUnFlushedSeqNo.clear();
349     oldestFlushingSeqNo.put(hri1.getEncodedNameAsBytes(), sequenceId1.get());
350     assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
351     // simulate region flush, i.e., clear oldestFlushing and oldestUnflushed maps
352     oldestFlushingSeqNo.clear();
353     oldestUnFlushedSeqNo.clear();
354     assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
355     // insert some large values for region1
356     oldestUnFlushedSeqNo.put(hri1.getEncodedNameAsBytes(), 1000l);
357     seqNo.put(hri1.getEncodedNameAsBytes(), 1500l);
358     assertFalse(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
359 
360     // tests when oldestUnFlushed/oldestFlushing contains larger value.
361     // It means region is flushed.
362     oldestFlushingSeqNo.put(hri1.getEncodedNameAsBytes(), 1200l);
363     oldestUnFlushedSeqNo.clear();
364     seqNo.put(hri1.getEncodedNameAsBytes(), 1199l);
365     assertTrue(FSHLog.areAllRegionsFlushed(seqNo, oldestFlushingSeqNo, oldestUnFlushedSeqNo));
366   }
367 
368   @Test(expected=IOException.class)
369   public void testFailedToCreateWALIfParentRenamed() throws IOException {
370     final String name = "testFailedToCreateWALIfParentRenamed";
371     FSHLog log = new FSHLog(fs, FSUtils.getRootDir(conf), name, HConstants.HREGION_OLDLOGDIR_NAME,
372         conf, null, true, null, null);
373     long filenum = System.currentTimeMillis();
374     Path path = log.computeFilename(filenum);
375     log.createWriterInstance(path);
376     Path parent = path.getParent();
377     path = log.computeFilename(filenum + 1);
378     Path newPath = new Path(parent.getParent(), parent.getName() + "-splitting");
379     fs.rename(parent, newPath);
380     log.createWriterInstance(path);
381     fail("It should fail to create the new WAL");
382   }
383 
384   /**
385    * Test flush for sure has a sequence id that is beyond the last edit appended.  We do this
386    * by slowing appends in the background ring buffer thread while in foreground we call
387    * flush.  The addition of the sync over HRegion in flush should fix an issue where flush was
388    * returning before all of its appends had made it out to the WAL (HBASE-11109).
389    * @throws IOException
390    * @see HBASE-11109
391    */
392   @Test
393   public void testFlushSequenceIdIsGreaterThanAllEditsInHFile() throws IOException {
394     String testName = "testFlushSequenceIdIsGreaterThanAllEditsInHFile";
395     final TableName tableName = TableName.valueOf(testName);
396     final HRegionInfo hri = new HRegionInfo(tableName);
397     final byte[] rowName = tableName.getName();
398     final HTableDescriptor htd = new HTableDescriptor(tableName);
399     htd.addFamily(new HColumnDescriptor("f"));
400     HRegion r = HRegion.createHRegion(hri, TEST_UTIL.getDefaultRootDirPath(),
401       TEST_UTIL.getConfiguration(), htd);
402     HRegion.closeHRegion(r);
403     final int countPerFamily = 10;
404     final MutableBoolean goslow = new MutableBoolean(false);
405     // subclass and doctor a method.
406     FSHLog wal = new FSHLog(FileSystem.get(conf), TEST_UTIL.getDefaultRootDirPath(),
407         testName, conf) {
408       @Override
409       void atHeadOfRingBufferEventHandlerAppend() {
410         if (goslow.isTrue()) {
411           Threads.sleep(100);
412           LOG.debug("Sleeping before appending 100ms");
413         }
414         super.atHeadOfRingBufferEventHandlerAppend();
415       }
416     };
417     HRegion region = HRegion.openHRegion(TEST_UTIL.getConfiguration(),
418       TEST_UTIL.getTestFileSystem(), TEST_UTIL.getDefaultRootDirPath(), hri, htd, wal);
419     EnvironmentEdge ee = EnvironmentEdgeManager.getDelegate();
420     try {
421       List<Put> puts = null;
422       for (HColumnDescriptor hcd: htd.getFamilies()) {
423         puts =
424           TestWALReplay.addRegionEdits(rowName, hcd.getName(), countPerFamily, ee, region, "x");
425       }
426 
427       // Now assert edits made it in.
428       final Get g = new Get(rowName);
429       Result result = region.get(g);
430       assertEquals(countPerFamily * htd.getFamilies().size(), result.size());
431 
432       // Construct a WALEdit and add it a few times to the WAL.
433       WALEdit edits = new WALEdit();
434       for (Put p: puts) {
435         CellScanner cs = p.cellScanner();
436         while (cs.advance()) {
437           edits.add(cs.current());
438         }
439       }
440       // Add any old cluster id.
441       List<UUID> clusterIds = new ArrayList<UUID>();
442       clusterIds.add(UUID.randomUUID());
443       // Now make appends run slow.
444       goslow.setValue(true);
445       for (int i = 0; i < countPerFamily; i++) {
446         final HRegionInfo info = region.getRegionInfo();
447         final WALKey logkey = new WALKey(info.getEncodedNameAsBytes(), tableName,
448             System.currentTimeMillis(), clusterIds, -1, -1);
449         wal.append(htd, info, logkey, edits, region.getSequenceId(), true, null);
450       }
451       region.flushcache();
452       // FlushResult.flushSequenceId is not visible here so go get the current sequence id.
453       long currentSequenceId = region.getSequenceId().get();
454       // Now release the appends
455       goslow.setValue(false);
456       synchronized (goslow) {
457         goslow.notifyAll();
458       }
459       assertTrue(currentSequenceId >= region.getSequenceId().get());
460     } finally {
461       region.close(true);
462       wal.close();
463     }
464   }
465 
466 }