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.master.snapshot;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.mockito.Mockito.mock;
024import static org.mockito.Mockito.when;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.HashSet;
031import java.util.List;
032import java.util.concurrent.atomic.AtomicInteger;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.fs.FileStatus;
035import org.apache.hadoop.fs.FileSystem;
036import org.apache.hadoop.fs.Path;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseTestingUtil;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
041import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
042import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils.SnapshotMock;
043import org.apache.hadoop.hbase.testclassification.LargeTests;
044import org.apache.hadoop.hbase.testclassification.MasterTests;
045import org.apache.hadoop.hbase.util.CommonFSUtils;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.BeforeClass;
049import org.junit.ClassRule;
050import org.junit.Test;
051import org.junit.experimental.categories.Category;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055import org.apache.hbase.thirdparty.com.google.common.collect.Iterables;
056import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
057
058import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
059
060/**
061 * Test that we correctly reload the cache, filter directories, etc.
062 */
063@Category({MasterTests.class, LargeTests.class})
064public class TestSnapshotFileCache {
065
066  @ClassRule
067  public static final HBaseClassTestRule CLASS_RULE =
068      HBaseClassTestRule.forClass(TestSnapshotFileCache.class);
069
070  protected static final Logger LOG = LoggerFactory.getLogger(TestSnapshotFileCache.class);
071  protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
072  // don't refresh the cache unless we tell it to
073  protected static final long PERIOD = Long.MAX_VALUE;
074  protected static FileSystem fs;
075  protected static Path rootDir;
076  protected static Path snapshotDir;
077  protected static Configuration conf;
078  protected static FileSystem workingFs;
079  protected static Path workingDir;
080
081  protected static void initCommon() throws Exception {
082    UTIL.startMiniDFSCluster(1);
083    fs = UTIL.getDFSCluster().getFileSystem();
084    rootDir = UTIL.getDefaultRootDirPath();
085    snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
086    conf = UTIL.getConfiguration();
087  }
088
089  @BeforeClass
090  public static void startCluster() throws Exception {
091    initCommon();
092    workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(rootDir, conf);
093    workingFs = workingDir.getFileSystem(conf);
094  }
095
096  @AfterClass
097  public static void stopCluster() throws Exception {
098    UTIL.shutdownMiniDFSCluster();
099  }
100
101  @After
102  public void cleanupFiles() throws Exception {
103    // cleanup the snapshot directory
104    fs.delete(snapshotDir, true);
105  }
106
107  @Test
108  public void testLoadAndDelete() throws IOException {
109    SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, workingFs, workingDir, PERIOD,
110      10000000, "test-snapshot-file-cache-refresh", new SnapshotFiles());
111
112    createAndTestSnapshotV1(cache, "snapshot1a", false, true, false);
113    createAndTestSnapshotV1(cache, "snapshot1b", true, true, false);
114
115    createAndTestSnapshotV2(cache, "snapshot2a", false, true, false);
116    createAndTestSnapshotV2(cache, "snapshot2b", true, true, false);
117  }
118
119  @Test
120  public void testReloadModifiedDirectory() throws IOException {
121    SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, workingFs, workingDir, PERIOD,
122      10000000, "test-snapshot-file-cache-refresh", new SnapshotFiles());
123
124    createAndTestSnapshotV1(cache, "snapshot1", false, true, false);
125    // now delete the snapshot and add a file with a different name
126    createAndTestSnapshotV1(cache, "snapshot1", false, false, false);
127
128    createAndTestSnapshotV2(cache, "snapshot2", false, true, false);
129    // now delete the snapshot and add a file with a different name
130    createAndTestSnapshotV2(cache, "snapshot2", false, false, false);
131  }
132
133  @Test
134  public void testSnapshotTempDirReload() throws IOException {
135    SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, workingFs, workingDir, PERIOD,
136      10000000, "test-snapshot-file-cache-refresh", new SnapshotFiles());
137
138    // Add a new non-tmp snapshot
139    createAndTestSnapshotV1(cache, "snapshot0v1", false, false, false);
140    createAndTestSnapshotV1(cache, "snapshot0v2", false, false, false);
141  }
142
143  @Test
144  public void testCacheUpdatedWhenLastModifiedOfSnapDirNotUpdated() throws IOException {
145    SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, workingFs, workingDir, PERIOD,
146      10000000, "test-snapshot-file-cache-refresh", new SnapshotFiles());
147
148    // Add a new non-tmp snapshot
149    createAndTestSnapshotV1(cache, "snapshot1v1", false, false, true);
150    createAndTestSnapshotV1(cache, "snapshot1v2", false, false, true);
151
152    // Add a new tmp snapshot
153    createAndTestSnapshotV2(cache, "snapshot2v1", true, false, true);
154
155    // Add another tmp snapshot
156    createAndTestSnapshotV2(cache, "snapshot2v2", true, false, true);
157  }
158
159  @Test
160  public void testWeNeverCacheTmpDirAndLoadIt() throws Exception {
161
162    final AtomicInteger count = new AtomicInteger(0);
163    // don't refresh the cache unless we tell it to
164    long period = Long.MAX_VALUE;
165    SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, workingFs, workingDir, period,
166      10000000, "test-snapshot-file-cache-refresh", new SnapshotFiles()) {
167      @Override
168      List<String> getSnapshotsInProgress()
169              throws IOException {
170        List<String> result = super.getSnapshotsInProgress();
171        count.incrementAndGet();
172        return result;
173      }
174
175      @Override public void triggerCacheRefreshForTesting() {
176        super.triggerCacheRefreshForTesting();
177      }
178    };
179
180    SnapshotMock.SnapshotBuilder complete =
181        createAndTestSnapshotV1(cache, "snapshot", false, false, false);
182
183    int countBeforeCheck = count.get();
184
185    CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
186
187    List<FileStatus> allStoreFiles = getStoreFilesForSnapshot(complete);
188    Iterable<FileStatus> deletableFiles = cache.getUnreferencedFiles(allStoreFiles, null);
189    assertTrue(Iterables.isEmpty(deletableFiles));
190    // no need for tmp dir check as all files are accounted for.
191    assertEquals(0, count.get() - countBeforeCheck);
192
193    // add a random file to make sure we refresh
194    FileStatus randomFile = mockStoreFile(UTIL.getRandomUUID().toString());
195    allStoreFiles.add(randomFile);
196    deletableFiles = cache.getUnreferencedFiles(allStoreFiles, null);
197    assertEquals(randomFile, Iterables.getOnlyElement(deletableFiles));
198    assertEquals(1, count.get() - countBeforeCheck); // we check the tmp directory
199  }
200
201  private List<FileStatus> getStoreFilesForSnapshot(SnapshotMock.SnapshotBuilder builder)
202      throws IOException {
203    final List<FileStatus> allStoreFiles = Lists.newArrayList();
204    SnapshotReferenceUtil
205        .visitReferencedFiles(conf, fs, builder.getSnapshotsDir(),
206            new SnapshotReferenceUtil.SnapshotVisitor() {
207              @Override public void storeFile(RegionInfo regionInfo, String familyName,
208                  SnapshotProtos.SnapshotRegionManifest.StoreFile storeFile) throws IOException {
209                FileStatus status = mockStoreFile(storeFile.getName());
210                allStoreFiles.add(status);
211              }
212            });
213    return allStoreFiles;
214  }
215
216  private FileStatus mockStoreFile(String storeFileName) {
217    FileStatus status = mock(FileStatus.class);
218    Path path = mock(Path.class);
219    when(path.getName()).thenReturn(storeFileName);
220    when(status.getPath()).thenReturn(path);
221    return status;
222  }
223
224  class SnapshotFiles implements SnapshotFileCache.SnapshotFileInspector {
225    @Override
226    public Collection<String> filesUnderSnapshot(final FileSystem workingFs,
227      final Path snapshotDir) throws IOException {
228      Collection<String> files =  new HashSet<>();
229      files.addAll(SnapshotReferenceUtil.getHFileNames(conf, workingFs, snapshotDir));
230      return files;
231    }
232  }
233
234  private SnapshotMock.SnapshotBuilder createAndTestSnapshotV1(final SnapshotFileCache cache,
235      final String name, final boolean tmp, final boolean removeOnExit, boolean setFolderTime)
236      throws IOException {
237    SnapshotMock snapshotMock = new SnapshotMock(conf, fs, rootDir);
238    SnapshotMock.SnapshotBuilder builder = snapshotMock.createSnapshotV1(name, name);
239    createAndTestSnapshot(cache, builder, tmp, removeOnExit, setFolderTime);
240    return builder;
241  }
242
243  private void createAndTestSnapshotV2(final SnapshotFileCache cache, final String name,
244      final boolean tmp, final boolean removeOnExit, boolean setFolderTime) throws IOException {
245    SnapshotMock snapshotMock = new SnapshotMock(conf, fs, rootDir);
246    SnapshotMock.SnapshotBuilder builder = snapshotMock.createSnapshotV2(name, name);
247    createAndTestSnapshot(cache, builder, tmp, removeOnExit, setFolderTime);
248  }
249
250  private void createAndTestSnapshot(final SnapshotFileCache cache,
251      final SnapshotMock.SnapshotBuilder builder,
252      final boolean tmp, final boolean removeOnExit, boolean setFolderTime) throws IOException {
253    List<Path> files = new ArrayList<>();
254    for (int i = 0; i < 3; ++i) {
255      for (Path filePath: builder.addRegion()) {
256        if (tmp) {
257          // We should be able to find all the files while the snapshot creation is in-progress
258          CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
259          assertFalse("Cache didn't find " + filePath,
260            contains(getNonSnapshotFiles(cache, filePath), filePath));
261        }
262        files.add(filePath);
263      }
264    }
265
266    // Finalize the snapshot
267    if (!tmp) {
268      builder.commit();
269    }
270
271    if (setFolderTime) {
272      fs.setTimes(snapshotDir, 0, -1);
273    }
274
275    // Make sure that all files are still present
276    for (Path path: files) {
277      assertFalse("Cache didn't find " + path, contains(getNonSnapshotFiles(cache, path), path));
278    }
279
280    CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
281    if (removeOnExit) {
282      LOG.debug("Deleting snapshot.");
283      builder.getSnapshotsDir().getFileSystem(conf).delete(builder.getSnapshotsDir(), true);
284      CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
285
286      // then trigger a refresh
287      cache.triggerCacheRefreshForTesting();
288      // and not it shouldn't find those files
289      for (Path filePath : files) {
290        assertTrue("Cache found '" + filePath + "', but it shouldn't have.",
291          contains(getNonSnapshotFiles(cache, filePath), filePath));
292
293      }
294    }
295  }
296
297  private static boolean contains(Iterable<FileStatus> files, Path filePath) {
298    for (FileStatus status: files) {
299      LOG.debug("debug in contains, 3.1: " + status.getPath() + " filePath:" + filePath);
300      if (filePath.equals(status.getPath())) {
301        return true;
302      }
303    }
304    return false;
305  }
306
307  private static Iterable<FileStatus> getNonSnapshotFiles(SnapshotFileCache cache, Path storeFile)
308    throws IOException {
309    return cache.getUnreferencedFiles(
310      Arrays.asList(CommonFSUtils.listStatus(fs, storeFile.getParent())), null);
311  }
312}