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