001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022
023import java.io.IOException;
024import java.util.List;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.FileSystem;
027import org.apache.hadoop.fs.Path;
028import org.apache.hadoop.hbase.Cell;
029import org.apache.hadoop.hbase.CellComparatorImpl;
030import org.apache.hadoop.hbase.CellScanner;
031import org.apache.hadoop.hbase.CellUtil;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtility;
034import org.apache.hadoop.hbase.HColumnDescriptor;
035import org.apache.hadoop.hbase.HConstants;
036import org.apache.hadoop.hbase.HRegionInfo;
037import org.apache.hadoop.hbase.HTableDescriptor;
038import org.apache.hadoop.hbase.MemoryCompactionPolicy;
039import org.apache.hadoop.hbase.PrivateCellUtil;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.client.Get;
042import org.apache.hadoop.hbase.client.Result;
043import org.apache.hadoop.hbase.io.hfile.CacheConfig;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.apache.hadoop.hbase.util.Bytes;
046import org.apache.hadoop.hbase.util.FSUtils;
047import org.apache.hadoop.hbase.wal.WAL;
048import org.apache.hadoop.hbase.wal.WALEdit;
049import org.apache.hadoop.hbase.wal.WALFactory;
050import org.apache.hadoop.hbase.wal.WALKey;
051import org.apache.hadoop.hbase.wal.WALSplitter;
052import org.junit.ClassRule;
053import org.junit.Rule;
054import org.junit.Test;
055import org.junit.experimental.categories.Category;
056import org.junit.rules.TestName;
057import org.slf4j.Logger;
058import org.slf4j.LoggerFactory;
059
060/**
061 * Tests around replay of recovered.edits content.
062 */
063@Category({MediumTests.class})
064public class TestRecoveredEdits {
065
066  @ClassRule
067  public static final HBaseClassTestRule CLASS_RULE =
068      HBaseClassTestRule.forClass(TestRecoveredEdits.class);
069
070  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
071  private static final Logger LOG = LoggerFactory.getLogger(TestRecoveredEdits.class);
072  @Rule public TestName testName = new TestName();
073
074  /**
075   * HBASE-12782 ITBLL fails for me if generator does anything but 5M per maptask.
076   * Create a region. Close it. Then copy into place a file to replay, one that is bigger than
077   * configured flush size so we bring on lots of flushes.  Then reopen and confirm all edits
078   * made it in.
079   * @throws IOException
080   */
081  @Test
082  public void testReplayWorksThoughLotsOfFlushing() throws
083      IOException {
084    CacheConfig.instantiateBlockCache(TEST_UTIL.getConfiguration());
085    for(MemoryCompactionPolicy policy : MemoryCompactionPolicy.values()) {
086      testReplayWorksWithMemoryCompactionPolicy(policy);
087    }
088  }
089
090  private void testReplayWorksWithMemoryCompactionPolicy(MemoryCompactionPolicy policy) throws
091    IOException {
092    Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
093    // Set it so we flush every 1M or so.  Thats a lot.
094    conf.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024*1024);
095    conf.set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, String.valueOf(policy));
096    // The file of recovered edits has a column family of 'meta'. Also has an encoded regionname
097    // of 4823016d8fca70b25503ee07f4c6d79f which needs to match on replay.
098    final String encodedRegionName = "4823016d8fca70b25503ee07f4c6d79f";
099    HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(testName.getMethodName()));
100    final String columnFamily = "meta";
101    byte [][] columnFamilyAsByteArray = new byte [][] {Bytes.toBytes(columnFamily)};
102    htd.addFamily(new HColumnDescriptor(columnFamily));
103    HRegionInfo hri = new HRegionInfo(htd.getTableName()) {
104      @Override
105      public synchronized String getEncodedName() {
106        return encodedRegionName;
107      }
108
109      // Cache the name because lots of lookups.
110      private byte [] encodedRegionNameAsBytes = null;
111      @Override
112      public synchronized byte[] getEncodedNameAsBytes() {
113        if (encodedRegionNameAsBytes == null) {
114          this.encodedRegionNameAsBytes = Bytes.toBytes(getEncodedName());
115        }
116        return this.encodedRegionNameAsBytes;
117      }
118    };
119    Path hbaseRootDir = TEST_UTIL.getDataTestDir();
120    ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null);
121    FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration());
122    Path tableDir = FSUtils.getTableDir(hbaseRootDir, htd.getTableName());
123    HRegionFileSystem hrfs =
124        new HRegionFileSystem(TEST_UTIL.getConfiguration(), fs, tableDir, hri);
125    if (fs.exists(hrfs.getRegionDir())) {
126      LOG.info("Region directory already exists. Deleting.");
127      fs.delete(hrfs.getRegionDir(), true);
128    }
129    HRegion region = HRegion.createHRegion(hri, hbaseRootDir, conf, htd, null);
130    assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName());
131    List<String> storeFiles = region.getStoreFileList(columnFamilyAsByteArray);
132    // There should be no store files.
133    assertTrue(storeFiles.isEmpty());
134    region.close();
135    Path regionDir = region.getRegionDir(hbaseRootDir, hri);
136    Path recoveredEditsDir = WALSplitter.getRegionDirRecoveredEditsDir(regionDir);
137    // This is a little fragile getting this path to a file of 10M of edits.
138    Path recoveredEditsFile = new Path(
139      System.getProperty("test.build.classes", "target/test-classes"),
140        "0000000000000016310");
141    // Copy this file under the region's recovered.edits dir so it is replayed on reopen.
142    Path destination = new Path(recoveredEditsDir, recoveredEditsFile.getName());
143    fs.copyToLocalFile(recoveredEditsFile, destination);
144    assertTrue(fs.exists(destination));
145    // Now the file 0000000000000016310 is under recovered.edits, reopen the region to replay.
146    region = HRegion.openHRegion(region, null);
147    assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName());
148    storeFiles = region.getStoreFileList(columnFamilyAsByteArray);
149    // Our 0000000000000016310 is 10MB. Most of the edits are for one region. Lets assume that if
150    // we flush at 1MB, that there are at least 3 flushed files that are there because of the
151    // replay of edits.
152    if(policy == MemoryCompactionPolicy.EAGER || policy == MemoryCompactionPolicy.ADAPTIVE) {
153      assertTrue("Files count=" + storeFiles.size(), storeFiles.size() >= 1);
154    } else {
155      assertTrue("Files count=" + storeFiles.size(), storeFiles.size() > 10);
156    }
157    // Now verify all edits made it into the region.
158    int count = verifyAllEditsMadeItIn(fs, conf, recoveredEditsFile, region);
159    LOG.info("Checked " + count + " edits made it in");
160  }
161
162  /**
163   * @param fs
164   * @param conf
165   * @param edits
166   * @param region
167   * @return Return how many edits seen.
168   * @throws IOException
169   */
170  private int verifyAllEditsMadeItIn(final FileSystem fs, final Configuration conf,
171      final Path edits, final HRegion region)
172  throws IOException {
173    int count = 0;
174    // Based on HRegion#replayRecoveredEdits
175    WAL.Reader reader = null;
176    try {
177      reader = WALFactory.createReader(fs, edits, conf);
178      WAL.Entry entry;
179      while ((entry = reader.next()) != null) {
180        WALKey key = entry.getKey();
181        WALEdit val = entry.getEdit();
182        count++;
183        // Check this edit is for this region.
184        if (!Bytes.equals(key.getEncodedRegionName(),
185            region.getRegionInfo().getEncodedNameAsBytes())) {
186          continue;
187        }
188        Cell previous = null;
189        for (Cell cell: val.getCells()) {
190          if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) continue;
191          if (previous != null && CellComparatorImpl.COMPARATOR.compareRows(previous, cell) == 0)
192            continue;
193          previous = cell;
194          Get g = new Get(CellUtil.cloneRow(cell));
195          Result r = region.get(g);
196          boolean found = false;
197          for (CellScanner scanner = r.cellScanner(); scanner.advance();) {
198            Cell current = scanner.current();
199            if (PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, cell,
200              current) == 0) {
201              found = true;
202              break;
203            }
204          }
205          assertTrue("Failed to find " + cell, found);
206        }
207      }
208    } finally {
209      if (reader != null) reader.close();
210    }
211    return count;
212  }
213}