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