/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.snapshot; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.errorhandling.ForeignExceptionListener; import org.apache.hadoop.hbase.errorhandling.TimeoutExceptionInjector; import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.Store; import org.apache.hadoop.hbase.regionserver.wal.HLog; import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.FSUtils; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * Utilities for useful when taking a snapshot */ public class TakeSnapshotUtils { private static final Log LOG = LogFactory.getLog(TakeSnapshotUtils.class); private TakeSnapshotUtils() { // private constructor for util class } /** * Get the per-region snapshot description location. * <p> * Under the per-snapshot directory, specific files per-region are kept in a similar layout as per * the current directory layout. * @param desc description of the snapshot * @param rootDir root directory for the hbase installation * @param regionName encoded name of the region (see {@link HRegionInfo#encodeRegionName(byte[])}) * @return path to the per-region directory for the snapshot */ public static Path getRegionSnapshotDirectory(SnapshotDescription desc, Path rootDir, String regionName) { Path snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir); return HRegion.getRegionDir(snapshotDir, regionName); } /** * Get the home directory for store-level snapshot files. * <p> * Specific files per store are kept in a similar layout as per the current directory layout. * @param regionDir snapshot directory for the parent region, <b>not</b> the standard region * directory. See {@link #getRegionSnapshotDirectory} * @param family name of the store to snapshot * @return path to the snapshot home directory for the store/family */ public static Path getStoreSnapshotDirectory(Path regionDir, String family) { return Store.getStoreHomedir(regionDir, Bytes.toBytes(family)); } /** * Get the snapshot directory for each family to be added to the the snapshot * @param snapshot description of the snapshot being take * @param snapshotRegionDir directory in the snapshot where the region directory information * should be stored * @param families families to be added (can be null) * @return paths to the snapshot directory for each family, in the same order as the families * passed in */ public static List<Path> getFamilySnapshotDirectories(SnapshotDescription snapshot, Path snapshotRegionDir, FileStatus[] families) { if (families == null || families.length == 0) return Collections.emptyList(); List<Path> familyDirs = new ArrayList<Path>(families.length); for (FileStatus family : families) { // build the reference directory name familyDirs.add(getStoreSnapshotDirectory(snapshotRegionDir, family.getPath().getName())); } return familyDirs; } /** * Create a snapshot timer for the master which notifies the monitor when an error occurs * @param snapshot snapshot to monitor * @param conf configuration to use when getting the max snapshot life * @param monitor monitor to notify when the snapshot life expires * @return the timer to use update to signal the start and end of the snapshot */ public static TimeoutExceptionInjector getMasterTimerAndBindToMonitor(SnapshotDescription snapshot, Configuration conf, ForeignExceptionListener monitor) { long maxTime = SnapshotDescriptionUtils.getMaxMasterTimeout(conf, snapshot.getType(), SnapshotDescriptionUtils.DEFAULT_MAX_WAIT_TIME); return new TimeoutExceptionInjector(monitor, maxTime); } /** * Verify that all the expected logs got referenced * @param fs filesystem where the logs live * @param logsDir original logs directory * @param serverNames names of the servers that involved in the snapshot * @param snapshot description of the snapshot being taken * @param snapshotLogDir directory for logs in the snapshot * @throws IOException */ public static void verifyAllLogsGotReferenced(FileSystem fs, Path logsDir, Set<String> serverNames, SnapshotDescription snapshot, Path snapshotLogDir) throws IOException { assertTrue(snapshot, "Logs directory doesn't exist in snapshot", fs.exists(logsDir)); // for each of the server log dirs, make sure it matches the main directory Multimap<String, String> snapshotLogs = getMapOfServersAndLogs(fs, snapshotLogDir, serverNames); Multimap<String, String> realLogs = getMapOfServersAndLogs(fs, logsDir, serverNames); if (realLogs != null) { assertNotNull(snapshot, "No server logs added to snapshot", snapshotLogs); } else { assertNull(snapshot, "Snapshotted server logs that don't exist", snapshotLogs); } // check the number of servers Set<Entry<String, Collection<String>>> serverEntries = realLogs.asMap().entrySet(); Set<Entry<String, Collection<String>>> snapshotEntries = snapshotLogs.asMap().entrySet(); assertEquals(snapshot, "Not the same number of snapshot and original server logs directories", serverEntries.size(), snapshotEntries.size()); // verify we snapshotted each of the log files for (Entry<String, Collection<String>> serverLogs : serverEntries) { // if the server is not the snapshot, skip checking its logs if (!serverNames.contains(serverLogs.getKey())) continue; Collection<String> snapshotServerLogs = snapshotLogs.get(serverLogs.getKey()); assertNotNull(snapshot, "Snapshots missing logs for server:" + serverLogs.getKey(), snapshotServerLogs); // check each of the log files assertEquals(snapshot, "Didn't reference all the log files for server:" + serverLogs.getKey(), serverLogs .getValue().size(), snapshotServerLogs.size()); for (String log : serverLogs.getValue()) { assertTrue(snapshot, "Snapshot logs didn't include " + log, snapshotServerLogs.contains(log)); } } } /** * Verify one of a snapshot's region's recovered.edits, has been at the surface (file names, * length), match the original directory. * @param fs filesystem on which the snapshot had been taken * @param rootDir full path to the root hbase directory * @param regionInfo info for the region * @param snapshot description of the snapshot that was taken * @throws IOException if there is an unexpected error talking to the filesystem */ public static void verifyRecoveredEdits(FileSystem fs, Path rootDir, HRegionInfo regionInfo, SnapshotDescription snapshot) throws IOException { Path regionDir = HRegion.getRegionDir(rootDir, regionInfo); Path editsDir = HLog.getRegionDirRecoveredEditsDir(regionDir); Path snapshotRegionDir = TakeSnapshotUtils.getRegionSnapshotDirectory(snapshot, rootDir, regionInfo.getEncodedName()); Path snapshotEditsDir = HLog.getRegionDirRecoveredEditsDir(snapshotRegionDir); FileStatus[] edits = FSUtils.listStatus(fs, editsDir); FileStatus[] snapshotEdits = FSUtils.listStatus(fs, snapshotEditsDir); if (edits == null) { assertNull(snapshot, "Snapshot has edits but table doesn't", snapshotEdits); return; } assertNotNull(snapshot, "Table has edits, but snapshot doesn't", snapshotEdits); // check each of the files assertEquals(snapshot, "Not same number of edits in snapshot as table", edits.length, snapshotEdits.length); // make sure we have a file with the same name as the original // it would be really expensive to verify the content matches the original for (FileStatus edit : edits) { for (FileStatus sEdit : snapshotEdits) { if (sEdit.getPath().equals(edit.getPath())) { assertEquals(snapshot, "Snapshot file" + sEdit.getPath() + " length not equal to the original: " + edit.getPath(), edit.getLen(), sEdit.getLen()); break; } } assertTrue(snapshot, "No edit in snapshot with name:" + edit.getPath(), false); } } private static void assertNull(SnapshotDescription snapshot, String msg, Object isNull) throws CorruptedSnapshotException { if (isNull != null) { throw new CorruptedSnapshotException(msg + ", Expected " + isNull + " to be null.", snapshot); } } private static void assertNotNull(SnapshotDescription snapshot, String msg, Object notNull) throws CorruptedSnapshotException { if (notNull == null) { throw new CorruptedSnapshotException(msg + ", Expected object to not be null, but was null.", snapshot); } } private static void assertTrue(SnapshotDescription snapshot, String msg, boolean isTrue) throws CorruptedSnapshotException { if (!isTrue) { throw new CorruptedSnapshotException(msg + ", Expected true, but was false", snapshot); } } /** * Assert that the expect matches the gotten amount * @param msg message to add the to exception * @param expected * @param gotten * @throws CorruptedSnapshotException thrown if the two elements don't match */ private static void assertEquals(SnapshotDescription snapshot, String msg, int expected, int gotten) throws CorruptedSnapshotException { if (expected != gotten) { throw new CorruptedSnapshotException(msg + ". Expected:" + expected + ", got:" + gotten, snapshot); } } /** * Assert that the expect matches the gotten amount * @param msg message to add the to exception * @param expected * @param gotten * @throws CorruptedSnapshotException thrown if the two elements don't match */ private static void assertEquals(SnapshotDescription snapshot, String msg, long expected, long gotten) throws CorruptedSnapshotException { if (expected != gotten) { throw new CorruptedSnapshotException(msg + ". Expected:" + expected + ", got:" + gotten, snapshot); } } /** * @param logdir * @param toInclude list of servers to include. If empty or null, returns all servers * @return maps of servers to all their log files. If there is no log directory, returns * <tt>null</tt> */ private static Multimap<String, String> getMapOfServersAndLogs(FileSystem fs, Path logdir, Collection<String> toInclude) throws IOException { // create a path filter based on the passed directories to include PathFilter filter = toInclude == null || toInclude.size() == 0 ? null : new MatchesDirectoryNames(toInclude); // get all the expected directories FileStatus[] serverLogDirs = FSUtils.listStatus(fs, logdir, filter); if (serverLogDirs == null) return null; // map those into a multimap of servername -> [log files] Multimap<String, String> map = HashMultimap.create(); for (FileStatus server : serverLogDirs) { FileStatus[] serverLogs = FSUtils.listStatus(fs, server.getPath(), null); if (serverLogs == null) continue; for (FileStatus log : serverLogs) { map.put(server.getPath().getName(), log.getPath().getName()); } } return map; } /** * Path filter that only accepts paths where that have a {@link Path#getName()} that is contained * in the specified collection. */ private static class MatchesDirectoryNames implements PathFilter { Collection<String> paths; public MatchesDirectoryNames(Collection<String> dirNames) { this.paths = dirNames; } @Override public boolean accept(Path path) { return paths.contains(path.getName()); } } /** * Get the log directory for a specific snapshot * @param snapshotDir directory where the specific snapshot will be store * @param serverName name of the parent regionserver for the log files * @return path to the log home directory for the archive files. */ public static Path getSnapshotHLogsDir(Path snapshotDir, String serverName) { return new Path(snapshotDir, HLog.getHLogDirectoryName(serverName)); } }