1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hbase.master.snapshot;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.Mockito.mock;
24 import static org.mockito.Mockito.when;
25 
26 import java.io.IOException;
27 import java.util.*;
28 import java.util.concurrent.atomic.AtomicInteger;
29 
30 import com.google.common.collect.Iterables;
31 import com.google.common.collect.Lists;
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.hadoop.fs.FileStatus;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.hbase.HBaseTestingUtility;
38 import org.apache.hadoop.hbase.HRegionInfo;
39 import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos;
40 import org.apache.hadoop.hbase.testclassification.MediumTests;
41 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
42 import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
43 import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
44 import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils.SnapshotMock;
45 import org.apache.hadoop.hbase.util.FSUtils;
46 import org.junit.After;
47 import org.junit.AfterClass;
48 import org.junit.BeforeClass;
49 import org.junit.Test;
50 import org.junit.experimental.categories.Category;
51 
52 /**
53  * Test that we correctly reload the cache, filter directories, etc.
54  */
55 @Category(MediumTests.class)
56 public class TestSnapshotFileCache {
57 
58   private static final Log LOG = LogFactory.getLog(TestSnapshotFileCache.class);
59   private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
60   private static long sequenceId = 0;
61   private static FileSystem fs;
62   private static Path rootDir;
63 
64   @BeforeClass
startCluster()65   public static void startCluster() throws Exception {
66     UTIL.startMiniDFSCluster(1);
67     fs = UTIL.getDFSCluster().getFileSystem();
68     rootDir = UTIL.getDefaultRootDirPath();
69   }
70 
71   @AfterClass
stopCluster()72   public static void stopCluster() throws Exception {
73     UTIL.shutdownMiniDFSCluster();
74   }
75 
76   @After
cleanupFiles()77   public void cleanupFiles() throws Exception {
78     // cleanup the snapshot directory
79     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
80     fs.delete(snapshotDir, true);
81   }
82 
83   @Test(timeout = 10000000)
testLoadAndDelete()84   public void testLoadAndDelete() throws IOException {
85     // don't refresh the cache unless we tell it to
86     long period = Long.MAX_VALUE;
87     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
88         "test-snapshot-file-cache-refresh", new SnapshotFiles());
89 
90     createAndTestSnapshotV1(cache, "snapshot1a", false, true);
91     createAndTestSnapshotV1(cache, "snapshot1b", true, true);
92 
93     createAndTestSnapshotV2(cache, "snapshot2a", false, true);
94     createAndTestSnapshotV2(cache, "snapshot2b", true, true);
95   }
96 
97   @Test
testJustFindLogsDirectory()98   public void testJustFindLogsDirectory() throws Exception {
99     // don't refresh the cache unless we tell it to
100     long period = Long.MAX_VALUE;
101     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
102     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
103         "test-snapshot-file-cache-refresh", new SnapshotFileCache.SnapshotFileInspector() {
104             public Collection<String> filesUnderSnapshot(final Path snapshotDir)
105                 throws IOException {
106               return SnapshotReferenceUtil.getWALNames(fs, snapshotDir);
107             }
108         });
109 
110     // create a file in a 'completed' snapshot
111     SnapshotDescription desc = SnapshotDescription.newBuilder().setName("snapshot").build();
112     Path snapshot = SnapshotDescriptionUtils.getCompletedSnapshotDir(desc, rootDir);
113     SnapshotDescriptionUtils.writeSnapshotInfo(desc, snapshot, fs);
114     Path file1 = new Path(new Path(new Path(snapshot, "7e91021"), "fam"), "file1");
115     fs.createNewFile(file1);
116 
117     // and another file in the logs directory
118     Path logs = SnapshotReferenceUtil.getLogsDir(snapshot, "server");
119     Path log = new Path(logs, "me.hbase.com%2C58939%2C1350424310315.1350424315552");
120     fs.createNewFile(log);
121 
122     FSUtils.logFileSystemState(fs, rootDir, LOG);
123 
124     // then make sure the cache only finds the log files
125     Iterable<FileStatus> notSnapshot = getNonSnapshotFiles(cache, file1);
126     assertFalse("Cache found '" + file1 + "', but it shouldn't have.",
127         Iterables.contains(notSnapshot, file1.getName()));
128     notSnapshot = getNonSnapshotFiles(cache, log);
129     assertTrue("Cache didn't find:" + log, !Iterables.contains(notSnapshot, log));
130   }
131 
132   @Test
testReloadModifiedDirectory()133   public void testReloadModifiedDirectory() throws IOException {
134     // don't refresh the cache unless we tell it to
135     long period = Long.MAX_VALUE;
136     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
137         "test-snapshot-file-cache-refresh", new SnapshotFiles());
138 
139     createAndTestSnapshotV1(cache, "snapshot1", false, true);
140     // now delete the snapshot and add a file with a different name
141     createAndTestSnapshotV1(cache, "snapshot1", false, false);
142 
143     createAndTestSnapshotV2(cache, "snapshot2", false, true);
144     // now delete the snapshot and add a file with a different name
145     createAndTestSnapshotV2(cache, "snapshot2", false, false);
146   }
147 
148   @Test
testSnapshotTempDirReload()149   public void testSnapshotTempDirReload() throws IOException {
150     long period = Long.MAX_VALUE;
151     // This doesn't refresh cache until we invoke it explicitly
152     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
153         "test-snapshot-file-cache-refresh", new SnapshotFiles());
154 
155     // Add a new non-tmp snapshot
156     createAndTestSnapshotV1(cache, "snapshot0v1", false, false);
157     createAndTestSnapshotV1(cache, "snapshot0v2", false, false);
158 
159     // Add a new tmp snapshot
160     createAndTestSnapshotV2(cache, "snapshot1", true, false);
161 
162     // Add another tmp snapshot
163     createAndTestSnapshotV2(cache, "snapshot2", true, false);
164   }
165 
166   @Test
testWeNeverCacheTmpDirAndLoadIt()167   public void testWeNeverCacheTmpDirAndLoadIt() throws Exception {
168 
169     final AtomicInteger count = new AtomicInteger(0);
170     // don't refresh the cache unless we tell it to
171     long period = Long.MAX_VALUE;
172     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
173         "test-snapshot-file-cache-refresh", new SnapshotFiles()) {
174       @Override
175       List<String> getSnapshotsInProgress() throws IOException {
176         List<String> result = super.getSnapshotsInProgress();
177         count.incrementAndGet();
178         return result;
179       }
180 
181       @Override public void triggerCacheRefreshForTesting() {
182         super.triggerCacheRefreshForTesting();
183       }
184     };
185 
186     SnapshotMock.SnapshotBuilder complete =
187         createAndTestSnapshotV1(cache, "snapshot", false, false);
188 
189     SnapshotMock.SnapshotBuilder inProgress =
190         createAndTestSnapshotV1(cache, "snapshotInProgress", true, false);
191 
192     int countBeforeCheck = count.get();
193 
194     FSUtils.logFileSystemState(fs, rootDir, LOG);
195 
196     List<FileStatus> allStoreFiles = getStoreFilesForSnapshot(complete);
197     Iterable<FileStatus> deletableFiles = cache.getUnreferencedFiles(allStoreFiles);
198     assertTrue(Iterables.isEmpty(deletableFiles));
199     // no need for tmp dir check as all files are accounted for.
200     assertEquals(0, count.get() - countBeforeCheck);
201 
202 
203     // add a random file to make sure we refresh
204     FileStatus randomFile = mockStoreFile(UUID.randomUUID().toString());
205     allStoreFiles.add(randomFile);
206     deletableFiles = cache.getUnreferencedFiles(allStoreFiles);
207     assertEquals(randomFile, Iterables.getOnlyElement(deletableFiles));
208     assertEquals(1, count.get() - countBeforeCheck); // we check the tmp directory
209   }
210 
getStoreFilesForSnapshot(SnapshotMock.SnapshotBuilder builder)211   private List<FileStatus> getStoreFilesForSnapshot(SnapshotMock.SnapshotBuilder builder)
212       throws IOException {
213     final List<FileStatus> allStoreFiles = Lists.newArrayList();
214     SnapshotReferenceUtil
215         .visitReferencedFiles(UTIL.getConfiguration(), fs, builder.getSnapshotsDir(),
216             new SnapshotReferenceUtil.SnapshotVisitor() {
217               @Override public void logFile(String server, String logfile) throws IOException {
218                 // do nothing.
219               }
220 
221               @Override public void storeFile(HRegionInfo regionInfo, String familyName,
222                   SnapshotProtos.SnapshotRegionManifest.StoreFile storeFile) throws IOException {
223                 FileStatus status = mockStoreFile(storeFile.getName());
224                 allStoreFiles.add(status);
225               }
226             });
227     return allStoreFiles;
228   }
229 
mockStoreFile(String storeFileName)230   private FileStatus mockStoreFile(String storeFileName) {
231     FileStatus status = mock(FileStatus.class);
232     Path path = mock(Path.class);
233     when(path.getName()).thenReturn(storeFileName);
234     when(status.getPath()).thenReturn(path);
235     return status;
236   }
237 
238   class SnapshotFiles implements SnapshotFileCache.SnapshotFileInspector {
filesUnderSnapshot(final Path snapshotDir)239     public Collection<String> filesUnderSnapshot(final Path snapshotDir) throws IOException {
240       Collection<String> files =  new HashSet<String>();
241       files.addAll(SnapshotReferenceUtil.getWALNames(fs, snapshotDir));
242       files.addAll(SnapshotReferenceUtil.getHFileNames(UTIL.getConfiguration(), fs, snapshotDir));
243       return files;
244     }
245   };
246 
createAndTestSnapshotV1(final SnapshotFileCache cache, final String name, final boolean tmp, final boolean removeOnExit)247   private SnapshotMock.SnapshotBuilder createAndTestSnapshotV1(final SnapshotFileCache cache,
248       final String name, final boolean tmp, final boolean removeOnExit) throws IOException {
249     SnapshotMock snapshotMock = new SnapshotMock(UTIL.getConfiguration(), fs, rootDir);
250     SnapshotMock.SnapshotBuilder builder = snapshotMock.createSnapshotV1(name, name);
251     createAndTestSnapshot(cache, builder, tmp, removeOnExit);
252     return builder;
253   }
254 
createAndTestSnapshotV2(final SnapshotFileCache cache, final String name, final boolean tmp, final boolean removeOnExit)255   private void createAndTestSnapshotV2(final SnapshotFileCache cache, final String name,
256       final boolean tmp, final boolean removeOnExit) throws IOException {
257     SnapshotMock snapshotMock = new SnapshotMock(UTIL.getConfiguration(), fs, rootDir);
258     SnapshotMock.SnapshotBuilder builder = snapshotMock.createSnapshotV2(name, name);
259     createAndTestSnapshot(cache, builder, tmp, removeOnExit);
260   }
261 
createAndTestSnapshot(final SnapshotFileCache cache, final SnapshotMock.SnapshotBuilder builder, final boolean tmp, final boolean removeOnExit)262   private void createAndTestSnapshot(final SnapshotFileCache cache,
263       final SnapshotMock.SnapshotBuilder builder,
264       final boolean tmp, final boolean removeOnExit) throws IOException {
265     List<Path> files = new ArrayList<Path>();
266     for (int i = 0; i < 3; ++i) {
267       for (Path filePath: builder.addRegion()) {
268         String fileName = filePath.getName();
269         if (tmp) {
270           // We should be able to find all the files while the snapshot creation is in-progress
271           FSUtils.logFileSystemState(fs, rootDir, LOG);
272           Iterable<FileStatus> nonSnapshot = getNonSnapshotFiles(cache, filePath);
273           assertFalse("Cache didn't find " + fileName, Iterables.contains(nonSnapshot, fileName));
274         }
275         files.add(filePath);
276       }
277     }
278 
279     // Finalize the snapshot
280     if (!tmp) {
281       builder.commit();
282     }
283 
284     // Make sure that all files are still present
285     for (Path path: files) {
286       Iterable<FileStatus> nonSnapshotFiles = getNonSnapshotFiles(cache, path);
287       assertFalse("Cache didn't find " + path.getName(),
288           Iterables.contains(nonSnapshotFiles, path.getName()));
289     }
290 
291     FSUtils.logFileSystemState(fs, rootDir, LOG);
292     if (removeOnExit) {
293       LOG.debug("Deleting snapshot.");
294       fs.delete(builder.getSnapshotsDir(), true);
295       FSUtils.logFileSystemState(fs, rootDir, LOG);
296 
297       // The files should be in cache until next refresh
298       for (Path filePath: files) {
299         Iterable<FileStatus> nonSnapshotFiles = getNonSnapshotFiles(cache, filePath);
300         assertFalse("Cache didn't find " + filePath.getName(), Iterables.contains(nonSnapshotFiles,
301             filePath.getName()));
302       }
303 
304       // then trigger a refresh
305       cache.triggerCacheRefreshForTesting();
306       // and not it shouldn't find those files
307       for (Path filePath: files) {
308         Iterable<FileStatus> nonSnapshotFiles = getNonSnapshotFiles(cache, filePath);
309         assertTrue("Cache found '" + filePath.getName() + "', but it shouldn't have.",
310             !Iterables.contains(nonSnapshotFiles, filePath.getName()));
311       }
312     }
313   }
314 
getNonSnapshotFiles(SnapshotFileCache cache, Path storeFile)315   private Iterable<FileStatus> getNonSnapshotFiles(SnapshotFileCache cache, Path storeFile)
316       throws IOException {
317     return cache.getUnreferencedFiles(
318         Arrays.asList(FSUtils.listStatus(fs, storeFile.getParent()))
319     );
320   }
321 }
322