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.ArrayList;
025import java.util.Collections;
026import java.util.List;
027
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.fs.FileSystem;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.Cell;
032import org.apache.hadoop.hbase.CellComparatorImpl;
033import org.apache.hadoop.hbase.CellUtil;
034import org.apache.hadoop.hbase.HBaseClassTestRule;
035import org.apache.hadoop.hbase.HBaseTestingUtility;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.HRegionInfo;
038import org.apache.hadoop.hbase.MemoryCompactionPolicy;
039import org.apache.hadoop.hbase.PrivateCellUtil;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
042import org.apache.hadoop.hbase.client.RegionInfo;
043import org.apache.hadoop.hbase.client.Scan;
044import org.apache.hadoop.hbase.client.TableDescriptor;
045import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
046import org.apache.hadoop.hbase.io.hfile.BlockCache;
047import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory;
048import org.apache.hadoop.hbase.testclassification.MediumTests;
049import org.apache.hadoop.hbase.util.Bytes;
050import org.apache.hadoop.hbase.util.FSUtils;
051import org.apache.hadoop.hbase.wal.WAL;
052import org.apache.hadoop.hbase.wal.WALEdit;
053import org.apache.hadoop.hbase.wal.WALFactory;
054import org.apache.hadoop.hbase.wal.WALKey;
055import org.apache.hadoop.hbase.wal.WALSplitUtil;
056import org.junit.BeforeClass;
057import org.junit.ClassRule;
058import org.junit.Rule;
059import org.junit.Test;
060import org.junit.experimental.categories.Category;
061import org.junit.rules.TestName;
062import org.slf4j.Logger;
063import org.slf4j.LoggerFactory;
064
065/**
066 * Tests around replay of recovered.edits content.
067 */
068@Category({MediumTests.class})
069public class TestRecoveredEdits {
070
071  @ClassRule
072  public static final HBaseClassTestRule CLASS_RULE =
073      HBaseClassTestRule.forClass(TestRecoveredEdits.class);
074
075  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
076  private static final Logger LOG = LoggerFactory.getLogger(TestRecoveredEdits.class);
077
078  private static BlockCache blockCache;
079
080  @Rule public TestName testName = new TestName();
081
082  @BeforeClass
083  public static void setUpBeforeClass() throws Exception {
084    blockCache = BlockCacheFactory.createBlockCache(TEST_UTIL.getConfiguration());
085  }
086
087  /**
088   * HBASE-12782 ITBLL fails for me if generator does anything but 5M per maptask.
089   * Create a region. Close it. Then copy into place a file to replay, one that is bigger than
090   * configured flush size so we bring on lots of flushes.  Then reopen and confirm all edits
091   * made it in.
092   * @throws IOException
093   */
094  @Test
095  public void testReplayWorksThoughLotsOfFlushing() throws
096      IOException {
097    for(MemoryCompactionPolicy policy : MemoryCompactionPolicy.values()) {
098      testReplayWorksWithMemoryCompactionPolicy(policy);
099    }
100  }
101
102  private void testReplayWorksWithMemoryCompactionPolicy(MemoryCompactionPolicy policy) throws
103    IOException {
104    Configuration conf = new Configuration(TEST_UTIL.getConfiguration());
105    // Set it so we flush every 1M or so.  Thats a lot.
106    conf.setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, 1024*1024);
107    conf.set(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_KEY, String.valueOf(policy).toLowerCase());
108    // The file of recovered edits has a column family of 'meta'. Also has an encoded regionname
109    // of 4823016d8fca70b25503ee07f4c6d79f which needs to match on replay.
110    final String encodedRegionName = "4823016d8fca70b25503ee07f4c6d79f";
111    final String columnFamily = "meta";
112    byte [][] columnFamilyAsByteArray = new byte [][] {Bytes.toBytes(columnFamily)};
113    TableDescriptor tableDescriptor =
114        TableDescriptorBuilder.newBuilder(TableName.valueOf(testName.getMethodName()))
115            .setColumnFamily(
116                ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily)).build())
117            .build();
118    RegionInfo hri = new HRegionInfo(tableDescriptor.getTableName()) {
119      @Override
120      public synchronized String getEncodedName() {
121        return encodedRegionName;
122      }
123
124      // Cache the name because lots of lookups.
125      private byte[] encodedRegionNameAsBytes = null;
126
127      @Override
128      public synchronized byte[] getEncodedNameAsBytes() {
129        if (encodedRegionNameAsBytes == null) {
130          this.encodedRegionNameAsBytes = Bytes.toBytes(getEncodedName());
131        }
132        return this.encodedRegionNameAsBytes;
133      }
134    };
135    Path hbaseRootDir = TEST_UTIL.getDataTestDir();
136    FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration());
137    Path tableDir = FSUtils.getTableDir(hbaseRootDir, tableDescriptor.getTableName());
138    HRegionFileSystem hrfs =
139        new HRegionFileSystem(TEST_UTIL.getConfiguration(), fs, tableDir, hri);
140    if (fs.exists(hrfs.getRegionDir())) {
141      LOG.info("Region directory already exists. Deleting.");
142      fs.delete(hrfs.getRegionDir(), true);
143    }
144    HRegion region = HBaseTestingUtility
145        .createRegionAndWAL(hri, hbaseRootDir, conf, tableDescriptor, blockCache);
146    assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName());
147    List<String> storeFiles = region.getStoreFileList(columnFamilyAsByteArray);
148    // There should be no store files.
149    assertTrue(storeFiles.isEmpty());
150    region.close();
151    Path regionDir = FSUtils.getRegionDirFromRootDir(hbaseRootDir, hri);
152    Path recoveredEditsDir = WALSplitUtil.getRegionDirRecoveredEditsDir(regionDir);
153    // This is a little fragile getting this path to a file of 10M of edits.
154    Path recoveredEditsFile = new Path(
155      System.getProperty("test.build.classes", "target/test-classes"),
156        "0000000000000016310");
157    // Copy this file under the region's recovered.edits dir so it is replayed on reopen.
158    Path destination = new Path(recoveredEditsDir, recoveredEditsFile.getName());
159    fs.copyToLocalFile(recoveredEditsFile, destination);
160    assertTrue(fs.exists(destination));
161    // Now the file 0000000000000016310 is under recovered.edits, reopen the region to replay.
162    region = HRegion.openHRegion(region, null);
163    assertEquals(encodedRegionName, region.getRegionInfo().getEncodedName());
164    storeFiles = region.getStoreFileList(columnFamilyAsByteArray);
165    // Our 0000000000000016310 is 10MB. Most of the edits are for one region. Lets assume that if
166    // we flush at 1MB, that there are at least 3 flushed files that are there because of the
167    // replay of edits.
168    if(policy == MemoryCompactionPolicy.EAGER || policy == MemoryCompactionPolicy.ADAPTIVE) {
169      assertTrue("Files count=" + storeFiles.size(), storeFiles.size() >= 1);
170    } else {
171      assertTrue("Files count=" + storeFiles.size(), storeFiles.size() > 10);
172    }
173    // Now verify all edits made it into the region.
174    int count = verifyAllEditsMadeItIn(fs, conf, recoveredEditsFile, region);
175    LOG.info("Checked " + count + " edits made it in");
176  }
177
178  /**
179   * @param fs
180   * @param conf
181   * @param edits
182   * @param region
183   * @return Return how many edits seen.
184   * @throws IOException
185   */
186  private int verifyAllEditsMadeItIn(final FileSystem fs, final Configuration conf,
187      final Path edits, final HRegion region) throws IOException {
188    int count = 0;
189    // Read all cells from recover edits
190    List<Cell> walCells = new ArrayList<>();
191    try (WAL.Reader reader = WALFactory.createReader(fs, edits, conf)) {
192      WAL.Entry entry;
193      while ((entry = reader.next()) != null) {
194        WALKey key = entry.getKey();
195        WALEdit val = entry.getEdit();
196        count++;
197        // Check this edit is for this region.
198        if (!Bytes
199            .equals(key.getEncodedRegionName(), region.getRegionInfo().getEncodedNameAsBytes())) {
200          continue;
201        }
202        Cell previous = null;
203        for (Cell cell : val.getCells()) {
204          if (CellUtil.matchingFamily(cell, WALEdit.METAFAMILY)) continue;
205          if (previous != null && CellComparatorImpl.COMPARATOR.compareRows(previous, cell) == 0)
206            continue;
207          previous = cell;
208          walCells.add(cell);
209        }
210      }
211    }
212
213    // Read all cells from region
214    List<Cell> regionCells = new ArrayList<>();
215    try (RegionScanner scanner = region.getScanner(new Scan())) {
216      List<Cell> tmpCells;
217      do {
218        tmpCells = new ArrayList<>();
219        scanner.nextRaw(tmpCells);
220        regionCells.addAll(tmpCells);
221      } while (!tmpCells.isEmpty());
222    }
223
224    Collections.sort(walCells, CellComparatorImpl.COMPARATOR);
225    int found = 0;
226    for (int i = 0, j = 0; i < walCells.size() && j < regionCells.size(); ) {
227      int compareResult = PrivateCellUtil
228          .compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, walCells.get(i),
229              regionCells.get(j));
230      if (compareResult == 0) {
231        i++;
232        j++;
233        found++;
234      } else if (compareResult > 0) {
235        j++;
236      } else {
237        i++;
238      }
239    }
240    assertEquals("Only found " + found + " cells in region, but there are " + walCells.size() +
241        " cells in recover edits", found, walCells.size());
242    return count;
243  }
244}