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