View Javadoc

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.snapshot;
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map.Entry;
26  import java.util.Set;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileStatus;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.fs.PathFilter;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.errorhandling.ForeignExceptionListener;
37  import org.apache.hadoop.hbase.errorhandling.TimeoutExceptionInjector;
38  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
39  import org.apache.hadoop.hbase.regionserver.HRegion;
40  import org.apache.hadoop.hbase.regionserver.Store;
41  import org.apache.hadoop.hbase.regionserver.wal.HLog;
42  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.FSUtils;
45  
46  import com.google.common.collect.HashMultimap;
47  import com.google.common.collect.Multimap;
48  
49  /**
50   * Utilities for useful when taking a snapshot
51   */
52  public class TakeSnapshotUtils {
53  
54    private static final Log LOG = LogFactory.getLog(TakeSnapshotUtils.class);
55  
56    private TakeSnapshotUtils() {
57      // private constructor for util class
58    }
59  
60    /**
61     * Get the per-region snapshot description location.
62     * <p>
63     * Under the per-snapshot directory, specific files per-region are kept in a similar layout as per
64     * the current directory layout.
65     * @param desc description of the snapshot
66     * @param rootDir root directory for the hbase installation
67     * @param regionName encoded name of the region (see {@link HRegionInfo#encodeRegionName(byte[])})
68     * @return path to the per-region directory for the snapshot
69     */
70    public static Path getRegionSnapshotDirectory(SnapshotDescription desc, Path rootDir,
71        String regionName) {
72      Path snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir);
73      return HRegion.getRegionDir(snapshotDir, regionName);
74    }
75  
76    /**
77     * Get the home directory for store-level snapshot files.
78     * <p>
79     * Specific files per store are kept in a similar layout as per the current directory layout.
80     * @param regionDir snapshot directory for the parent region, <b>not</b> the standard region
81     *          directory. See {@link #getRegionSnapshotDirectory}
82     * @param family name of the store to snapshot
83     * @return path to the snapshot home directory for the store/family
84     */
85    public static Path getStoreSnapshotDirectory(Path regionDir, String family) {
86      return Store.getStoreHomedir(regionDir, Bytes.toBytes(family));
87    }
88  
89    /**
90     * Get the snapshot directory for each family to be added to the the snapshot
91     * @param snapshot description of the snapshot being take
92     * @param snapshotRegionDir directory in the snapshot where the region directory information
93     *          should be stored
94     * @param families families to be added (can be null)
95     * @return paths to the snapshot directory for each family, in the same order as the families
96     *         passed in
97     */
98    public static List<Path> getFamilySnapshotDirectories(SnapshotDescription snapshot,
99        Path snapshotRegionDir, FileStatus[] families) {
100     if (families == null || families.length == 0) return Collections.emptyList();
101 
102     List<Path> familyDirs = new ArrayList<Path>(families.length);
103     for (FileStatus family : families) {
104       // build the reference directory name
105       familyDirs.add(getStoreSnapshotDirectory(snapshotRegionDir, family.getPath().getName()));
106     }
107     return familyDirs;
108   }
109 
110   /**
111    * Create a snapshot timer for the master which notifies the monitor when an error occurs
112    * @param snapshot snapshot to monitor
113    * @param conf configuration to use when getting the max snapshot life
114    * @param monitor monitor to notify when the snapshot life expires
115    * @return the timer to use update to signal the start and end of the snapshot
116    */
117   public static TimeoutExceptionInjector getMasterTimerAndBindToMonitor(SnapshotDescription snapshot,
118       Configuration conf, ForeignExceptionListener monitor) {
119     long maxTime = SnapshotDescriptionUtils.getMaxMasterTimeout(conf, snapshot.getType(),
120       SnapshotDescriptionUtils.DEFAULT_MAX_WAIT_TIME);
121     return new TimeoutExceptionInjector(monitor, maxTime);
122   }
123 
124   /**
125    * Verify that all the expected logs got referenced
126    * @param fs filesystem where the logs live
127    * @param logsDir original logs directory
128    * @param serverNames names of the servers that involved in the snapshot
129    * @param snapshot description of the snapshot being taken
130    * @param snapshotLogDir directory for logs in the snapshot
131    * @throws IOException
132    */
133   public static void verifyAllLogsGotReferenced(FileSystem fs, Path logsDir,
134       Set<String> serverNames, SnapshotDescription snapshot, Path snapshotLogDir)
135       throws IOException {
136     assertTrue(snapshot, "Logs directory doesn't exist in snapshot", fs.exists(logsDir));
137     // for each of the server log dirs, make sure it matches the main directory
138     Multimap<String, String> snapshotLogs = getMapOfServersAndLogs(fs, snapshotLogDir, serverNames);
139     Multimap<String, String> realLogs = getMapOfServersAndLogs(fs, logsDir, serverNames);
140     if (realLogs != null) {
141       assertNotNull(snapshot, "No server logs added to snapshot", snapshotLogs);
142     } else {
143       assertNull(snapshot, "Snapshotted server logs that don't exist", snapshotLogs);
144     }
145 
146     // check the number of servers
147     Set<Entry<String, Collection<String>>> serverEntries = realLogs.asMap().entrySet();
148     Set<Entry<String, Collection<String>>> snapshotEntries = snapshotLogs.asMap().entrySet();
149     assertEquals(snapshot, "Not the same number of snapshot and original server logs directories",
150       serverEntries.size(), snapshotEntries.size());
151 
152     // verify we snapshotted each of the log files
153     for (Entry<String, Collection<String>> serverLogs : serverEntries) {
154       // if the server is not the snapshot, skip checking its logs
155       if (!serverNames.contains(serverLogs.getKey())) continue;
156       Collection<String> snapshotServerLogs = snapshotLogs.get(serverLogs.getKey());
157       assertNotNull(snapshot, "Snapshots missing logs for server:" + serverLogs.getKey(),
158         snapshotServerLogs);
159 
160       // check each of the log files
161       assertEquals(snapshot,
162         "Didn't reference all the log files for server:" + serverLogs.getKey(), serverLogs
163             .getValue().size(), snapshotServerLogs.size());
164       for (String log : serverLogs.getValue()) {
165         assertTrue(snapshot, "Snapshot logs didn't include " + log,
166           snapshotServerLogs.contains(log));
167       }
168     }
169   }
170 
171   /**
172    * Verify one of a snapshot's region's recovered.edits, has been at the surface (file names,
173    * length), match the original directory.
174    * @param fs filesystem on which the snapshot had been taken
175    * @param rootDir full path to the root hbase directory
176    * @param regionInfo info for the region
177    * @param snapshot description of the snapshot that was taken
178    * @throws IOException if there is an unexpected error talking to the filesystem
179    */
180   public static void verifyRecoveredEdits(FileSystem fs, Path rootDir, HRegionInfo regionInfo,
181       SnapshotDescription snapshot) throws IOException {
182     Path regionDir = HRegion.getRegionDir(rootDir, regionInfo);
183     Path editsDir = HLog.getRegionDirRecoveredEditsDir(regionDir);
184     Path snapshotRegionDir = TakeSnapshotUtils.getRegionSnapshotDirectory(snapshot, rootDir,
185       regionInfo.getEncodedName());
186     Path snapshotEditsDir = HLog.getRegionDirRecoveredEditsDir(snapshotRegionDir);
187 
188     FileStatus[] edits = FSUtils.listStatus(fs, editsDir);
189     FileStatus[] snapshotEdits = FSUtils.listStatus(fs, snapshotEditsDir);
190     if (edits == null) {
191       assertNull(snapshot, "Snapshot has edits but table doesn't", snapshotEdits);
192       return;
193     }
194 
195     assertNotNull(snapshot, "Table has edits, but snapshot doesn't", snapshotEdits);
196 
197     // check each of the files
198     assertEquals(snapshot, "Not same number of edits in snapshot as table", edits.length,
199       snapshotEdits.length);
200 
201     // make sure we have a file with the same name as the original
202     // it would be really expensive to verify the content matches the original
203     for (FileStatus edit : edits) {
204       for (FileStatus sEdit : snapshotEdits) {
205         if (sEdit.getPath().equals(edit.getPath())) {
206           assertEquals(snapshot, "Snapshot file" + sEdit.getPath()
207               + " length not equal to the original: " + edit.getPath(), edit.getLen(),
208             sEdit.getLen());
209           break;
210         }
211       }
212       assertTrue(snapshot, "No edit in snapshot with name:" + edit.getPath(), false);
213     }
214   }
215 
216   private static void assertNull(SnapshotDescription snapshot, String msg, Object isNull)
217       throws CorruptedSnapshotException {
218     if (isNull != null) {
219       throw new CorruptedSnapshotException(msg + ", Expected " + isNull + " to be null.", snapshot);
220     }
221   }
222 
223   private static void assertNotNull(SnapshotDescription snapshot, String msg, Object notNull)
224       throws CorruptedSnapshotException {
225     if (notNull == null) {
226       throw new CorruptedSnapshotException(msg + ", Expected object to not be null, but was null.",
227           snapshot);
228     }
229   }
230 
231   private static void assertTrue(SnapshotDescription snapshot, String msg, boolean isTrue)
232       throws CorruptedSnapshotException {
233     if (!isTrue) {
234       throw new CorruptedSnapshotException(msg + ", Expected true, but was false", snapshot);
235     }
236   }
237 
238   /**
239    * Assert that the expect matches the gotten amount
240    * @param msg message to add the to exception
241    * @param expected
242    * @param gotten
243    * @throws CorruptedSnapshotException thrown if the two elements don't match
244    */
245   private static void assertEquals(SnapshotDescription snapshot, String msg, int expected,
246       int gotten) throws CorruptedSnapshotException {
247     if (expected != gotten) {
248       throw new CorruptedSnapshotException(msg + ". Expected:" + expected + ", got:" + gotten,
249           snapshot);
250     }
251   }
252 
253   /**
254    * Assert that the expect matches the gotten amount
255    * @param msg message to add the to exception
256    * @param expected
257    * @param gotten
258    * @throws CorruptedSnapshotException thrown if the two elements don't match
259    */
260   private static void assertEquals(SnapshotDescription snapshot, String msg, long expected,
261       long gotten) throws CorruptedSnapshotException {
262     if (expected != gotten) {
263       throw new CorruptedSnapshotException(msg + ". Expected:" + expected + ", got:" + gotten,
264           snapshot);
265     }
266   }
267 
268   /**
269    * @param logdir
270    * @param toInclude list of servers to include. If empty or null, returns all servers
271    * @return maps of servers to all their log files. If there is no log directory, returns
272    *         <tt>null</tt>
273    */
274   private static Multimap<String, String> getMapOfServersAndLogs(FileSystem fs, Path logdir,
275       Collection<String> toInclude) throws IOException {
276     // create a path filter based on the passed directories to include
277     PathFilter filter = toInclude == null || toInclude.size() == 0 ? null
278         : new MatchesDirectoryNames(toInclude);
279 
280     // get all the expected directories
281     FileStatus[] serverLogDirs = FSUtils.listStatus(fs, logdir, filter);
282     if (serverLogDirs == null) return null;
283 
284     // map those into a multimap of servername -> [log files]
285     Multimap<String, String> map = HashMultimap.create();
286     for (FileStatus server : serverLogDirs) {
287       FileStatus[] serverLogs = FSUtils.listStatus(fs, server.getPath(), null);
288       if (serverLogs == null) continue;
289       for (FileStatus log : serverLogs) {
290         map.put(server.getPath().getName(), log.getPath().getName());
291       }
292     }
293     return map;
294   }
295 
296   /**
297    * Path filter that only accepts paths where that have a {@link Path#getName()} that is contained
298    * in the specified collection.
299    */
300   private static class MatchesDirectoryNames implements PathFilter {
301 
302     Collection<String> paths;
303 
304     public MatchesDirectoryNames(Collection<String> dirNames) {
305       this.paths = dirNames;
306     }
307 
308     @Override
309     public boolean accept(Path path) {
310       return paths.contains(path.getName());
311     }
312   }
313 
314   /**
315    * Get the log directory for a specific snapshot
316    * @param snapshotDir directory where the specific snapshot will be store
317    * @param serverName name of the parent regionserver for the log files
318    * @return path to the log home directory for the archive files.
319    */
320   public static Path getSnapshotHLogsDir(Path snapshotDir, String serverName) {
321     return new Path(snapshotDir, HLog.getHLogDirectoryName(serverName));
322   }
323 }