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  
24  import java.io.IOException;
25  import java.util.*;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import com.google.common.collect.Iterables;
29  import com.google.common.collect.ObjectArrays;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.fs.FileStatus;
33  import org.apache.hadoop.fs.FileSystem;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.MediumTests;
37  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
38  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
39  import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
40  import org.apache.hadoop.hbase.snapshot.TakeSnapshotUtils;
41  import org.apache.hadoop.hbase.util.FSUtils;
42  import org.junit.After;
43  import org.junit.AfterClass;
44  import org.junit.BeforeClass;
45  import org.junit.Test;
46  import org.junit.experimental.categories.Category;
47  
48  /**
49   * Test that we correctly reload the cache, filter directories, etc.
50   */
51  @Category(MediumTests.class)
52  public class TestSnapshotFileCache {
53  
54    private static final Log LOG = LogFactory.getLog(TestSnapshotFileCache.class);
55    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
56    private static FileSystem fs;
57    private static Path rootDir;
58  
59    @BeforeClass
60    public static void startCluster() throws Exception {
61      UTIL.startMiniDFSCluster(1);
62      fs = UTIL.getDFSCluster().getFileSystem();
63      rootDir = UTIL.getDefaultRootDirPath();
64    }
65  
66    @AfterClass
67    public static void stopCluster() throws Exception {
68      UTIL.shutdownMiniDFSCluster();
69    }
70  
71    @After
72    public void cleanupFiles() throws Exception {
73      // cleanup the snapshot directory
74      Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
75      fs.delete(snapshotDir, true);
76    }
77  
78    @Test(timeout = 10000000)
79    public void testLoadAndDelete() throws Exception {
80      // don't refresh the cache unless we tell it to
81      long period = Long.MAX_VALUE;
82      Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
83      SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
84          "test-snapshot-file-cache-refresh", new SnapshotFiles());
85  
86      Path snapshot = new Path(snapshotDir, "snapshot");
87      Path region = new Path(snapshot, "7e91021");
88      Path family = new Path(region, "fam");
89      Path file1 = new Path(family, "file1");
90      Path file2 = new Path(family, "file2");
91  
92  
93      // create two hfiles under the snapshot
94      fs.createNewFile(file1);
95      fs.createNewFile(file2);
96  
97      FSUtils.logFileSystemState(fs, rootDir, LOG);
98  
99      // then make sure the cache finds them
100     Iterable<FileStatus> nonSnapshotFiles = cache.getUnreferencedFiles(
101             Arrays.asList(FSUtils.listStatus(fs, family))
102     );
103     assertFalse("Cache didn't find:" + file1, Iterables.contains(nonSnapshotFiles, file1));
104     assertFalse("Cache didn't find:" + file2, Iterables.contains(nonSnapshotFiles, file2));
105     String not = "file-shouldn't-be-found";
106     assertFalse("Cache found '" + not + "', but it shouldn't have.", Iterables.contains(nonSnapshotFiles, not));
107 
108     // make sure we get a little bit of separation in the modification times
109     // its okay if we sleep a little longer (b/c of GC pause), as long as we sleep a little
110     Thread.sleep(10);
111 
112     LOG.debug("Deleting snapshot.");
113     // then delete the snapshot and make sure that we can still find the files
114     if (!fs.delete(snapshot, true)) {
115       throw new IOException("Couldn't delete " + snapshot + " for an unknown reason.");
116     }
117     FSUtils.logFileSystemState(fs, rootDir, LOG);
118 
119 
120     LOG.debug("Checking to see if file is deleted.");
121     nonSnapshotFiles = cache.getUnreferencedFiles(
122             nonSnapshotFiles
123     );
124     
125     assertFalse("Cache didn't find:" + file1, Iterables.contains(nonSnapshotFiles, file1));
126     assertFalse("Cache didn't find:" + file2, Iterables.contains(nonSnapshotFiles, file2));
127 
128     // then trigger a refresh
129     cache.triggerCacheRefreshForTesting();
130 
131     nonSnapshotFiles = cache.getUnreferencedFiles(
132             nonSnapshotFiles
133     );
134     // and not it shouldn't find those files
135     assertFalse("Cache found '" + file1 + "', but it shouldn't have.",
136             Iterables.contains(nonSnapshotFiles, file1));
137     assertFalse("Cache found '" + file2 + "', but it shouldn't have.",
138             Iterables.contains(nonSnapshotFiles, file2));
139 
140     fs.delete(snapshotDir, true);
141   }
142 
143   @Test
144   public void testWeNeverCacheTmpDirAndLoadIt() throws Exception {
145 
146     final AtomicInteger count = new AtomicInteger(0);
147     // don't refresh the cache unless we tell it to
148     long period = Long.MAX_VALUE;
149     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
150     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
151             "test-snapshot-file-cache-refresh", new SnapshotFiles()) {
152       @Override
153       List<String> getSnapshotsInProgress() throws IOException {
154         List<String> result = super.getSnapshotsInProgress();
155         count.incrementAndGet();
156         return result;
157       }
158     };
159 
160     // create a file in a 'completed' snapshot
161     Path snapshot = new Path(snapshotDir, "snapshot");
162     Path region = new Path(snapshot, "7e91021");
163     Path family = new Path(region, "fam");
164     Path file1 = new Path(family, "file1");
165     fs.createNewFile(file1);
166 
167     FileStatus[] completedFiles = FSUtils.listStatus(fs, family);
168 
169     // create an 'in progress' snapshot
170     SnapshotDescription desc = SnapshotDescription.newBuilder().setName("working").build();
171     snapshot = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir);
172     region = new Path(snapshot, "7e91021");
173     family = new Path(region, "fam");
174     Path file2 = new Path(family, "file2");
175     fs.createNewFile(file2);
176     cache.triggerCacheRefreshForTesting();
177 
178     Iterable<FileStatus> deletableFiles = cache.getUnreferencedFiles(Arrays.asList(
179             ObjectArrays.concat(completedFiles, FSUtils.listStatus(fs, family), FileStatus.class))
180     );
181     assertTrue(Iterables.isEmpty(deletableFiles));
182     assertEquals(1, count.get()); // we check the tmp directory
183 
184     Path file3 = new Path(family, "file3");
185     fs.create(file3);
186     deletableFiles = cache.getUnreferencedFiles(Arrays.asList(
187             ObjectArrays.concat(completedFiles, FSUtils.listStatus(fs, family), FileStatus.class))
188     );
189     assertTrue(Iterables.isEmpty(deletableFiles));
190     assertEquals(2, count.get()); // we check the tmp directory
191 
192   }
193 
194   @Test
195   public void testLoadsTmpDir() throws Exception {
196     // don't refresh the cache unless we tell it to
197     long period = Long.MAX_VALUE;
198     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
199     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
200         "test-snapshot-file-cache-refresh", new SnapshotFiles());
201 
202     // create a file in a 'completed' snapshot
203     Path snapshot = new Path(snapshotDir, "snapshot");
204     Path region = new Path(snapshot, "7e91021");
205     Path family = new Path(region, "fam");
206     Path file1 = new Path(family, "file1");
207     fs.createNewFile(file1);
208 
209     // create an 'in progress' snapshot
210     SnapshotDescription desc = SnapshotDescription.newBuilder().setName("working").build();
211     snapshot = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir);
212     region = new Path(snapshot, "7e91021");
213     family = new Path(region, "fam");
214     Path file2 = new Path(family, "file2");
215     fs.createNewFile(file2);
216 
217     FSUtils.logFileSystemState(fs, rootDir, LOG);
218 
219     // then make sure the cache finds both files
220     Iterable<FileStatus> nonSnapshotFiles = cache.getUnreferencedFiles(
221             Arrays.asList(FSUtils.listStatus(fs, family))
222     );
223     assertFalse("Cache didn't find:" + file1, Iterables.contains(nonSnapshotFiles, file1));
224     assertFalse("Cache didn't find:" + file2, Iterables.contains(nonSnapshotFiles, file2));
225   }
226 
227   @Test
228   public void testJustFindLogsDirectory() throws Exception {
229     // don't refresh the cache unless we tell it to
230     long period = Long.MAX_VALUE;
231     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
232     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
233         "test-snapshot-file-cache-refresh", new SnapshotFileCache.SnapshotFileInspector() {
234             public Collection<String> filesUnderSnapshot(final Path snapshotDir)
235                 throws IOException {
236               return SnapshotReferenceUtil.getHLogNames(fs, snapshotDir);
237             }
238         });
239 
240     // create a file in a 'completed' snapshot
241     Path snapshot = new Path(snapshotDir, "snapshot");
242     Path region = new Path(snapshot, "7e91021");
243     Path family = new Path(region, "fam");
244     Path file1 = new Path(family, "file1");
245     fs.createNewFile(file1);
246 
247     // and another file in the logs directory
248     Path logs = TakeSnapshotUtils.getSnapshotHLogsDir(snapshot, "server");
249     Path log = new Path(logs, "me.hbase.com%2C58939%2C1350424310315.1350424315552");
250     fs.createNewFile(log);
251 
252     FSUtils.logFileSystemState(fs, rootDir, LOG);
253 
254     Iterable<FileStatus> nonSnapshotFiles = cache.getUnreferencedFiles(
255             Arrays.asList(FSUtils.listStatus(fs, family))
256     );    
257     // then make sure the cache only finds the log files
258     assertFalse("Cache found '" + file1 + "', but it shouldn't have.",
259             Iterables.contains(nonSnapshotFiles, file1));
260     assertFalse("Cache didn't find:" + log, Iterables.contains(nonSnapshotFiles, log));
261   }
262 
263   @Test
264   public void testReloadModifiedDirectory() throws IOException {
265     // don't refresh the cache unless we tell it to
266     long period = Long.MAX_VALUE;
267     Path snapshotDir = SnapshotDescriptionUtils.getSnapshotsDir(rootDir);
268     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
269         "test-snapshot-file-cache-refresh", new SnapshotFiles());
270 
271     Path snapshot = new Path(snapshotDir, "snapshot");
272     Path region = new Path(snapshot, "7e91021");
273     Path family = new Path(region, "fam");
274     Path file1 = new Path(family, "file1");
275     Path file2 = new Path(family, "file2");
276 
277     // create two hfiles under the snapshot
278     fs.createNewFile(file1);
279     fs.createNewFile(file2);
280 
281     FSUtils.logFileSystemState(fs, rootDir, LOG);
282 
283     Iterable<FileStatus> nonSnapshotFiles = cache.getUnreferencedFiles(
284             Arrays.asList(FSUtils.listStatus(fs, family))
285     );  
286     assertFalse("Cache didn't find " + file1, Iterables.contains(nonSnapshotFiles, file1));
287 
288     // now delete the snapshot and add a file with a different name
289     fs.delete(snapshot, true);
290     Path file3 = new Path(family, "new_file");
291     fs.createNewFile(file3);
292 
293     FSUtils.logFileSystemState(fs, rootDir, LOG);
294     nonSnapshotFiles = cache.getUnreferencedFiles(
295             Arrays.asList(FSUtils.listStatus(fs, family))
296     );
297     assertFalse("Cache didn't find new file:" + file3, Iterables.contains(nonSnapshotFiles, file3));
298   }
299 
300   @Test
301   public void testSnapshotTempDirReload() throws IOException {
302     long period = Long.MAX_VALUE;
303     Path snapshotDir = new Path(SnapshotDescriptionUtils.getSnapshotsDir(rootDir),
304         SnapshotDescriptionUtils.SNAPSHOT_TMP_DIR_NAME);
305     SnapshotFileCache cache = new SnapshotFileCache(fs, rootDir, period, 10000000,
306         "test-snapshot-file-cache-refresh", new SnapshotFiles());
307 
308     // Add a new snapshot
309     Path snapshot1 = new Path(snapshotDir, "snapshot1");
310     Path file1 = new Path(new Path(new Path(snapshot1, "7e91021"), "fam"), "file1");
311     fs.createNewFile(file1);
312     assertTrue(cache.getSnapshotsInProgress().contains(file1.getName()));
313 
314     // Add another snapshot
315     Path snapshot2 = new Path(snapshotDir, "snapshot2");
316     Path file2 = new Path(new Path(new Path(snapshot2, "7e91021"), "fam2"), "file2");
317     fs.createNewFile(file2);
318     assertTrue(cache.getSnapshotsInProgress().contains((file2.getName())));
319   }
320 
321   class SnapshotFiles implements SnapshotFileCache.SnapshotFileInspector {
322     public Collection<String> filesUnderSnapshot(final Path snapshotDir) throws IOException {
323       Collection<String> files =  new HashSet<String>();
324       files.addAll(SnapshotReferenceUtil.getHLogNames(fs, snapshotDir));
325       files.addAll(SnapshotReferenceUtil.getHFileNames(fs, snapshotDir));
326       return files;
327     }
328   };
329 }