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