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 com.google.common.annotations.VisibleForTesting;
21  import java.io.IOException;
22  import java.net.URI;
23  import java.security.PrivilegedExceptionAction;
24  import java.util.Collections;
25  
26  import com.google.common.collect.ListMultimap;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FSDataInputStream;
31  import org.apache.hadoop.fs.FSDataOutputStream;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.FileUtil;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.fs.permission.FsPermission;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.TableName;
38  import org.apache.hadoop.hbase.classification.InterfaceAudience;
39  import org.apache.hadoop.hbase.client.Admin;
40  import org.apache.hadoop.hbase.client.Connection;
41  import org.apache.hadoop.hbase.client.ConnectionFactory;
42  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
43  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
44  import org.apache.hadoop.hbase.security.User;
45  import org.apache.hadoop.hbase.security.access.AccessControlLists;
46  import org.apache.hadoop.hbase.security.access.TablePermission;
47  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
48  import org.apache.hadoop.hbase.util.FSUtils;
49  
50  /**
51   * Utility class to help manage {@link SnapshotDescription SnapshotDesriptions}.
52   * <p>
53   * Snapshots are laid out on disk like this:
54   *
55   * <pre>
56   * /hbase/.snapshots
57   *          /.tmp                &lt;---- working directory
58   *          /[snapshot name]     &lt;----- completed snapshot
59   * </pre>
60   *
61   * A completed snapshot named 'completed' then looks like (multiple regions, servers, files, etc.
62   * signified by '...' on the same directory depth).
63   *
64   * <pre>
65   * /hbase/.snapshots/completed
66   *                   .snapshotinfo          &lt;--- Description of the snapshot
67   *                   .tableinfo             &lt;--- Copy of the tableinfo
68   *                    /.logs
69   *                        /[server_name]
70   *                            /... [log files]
71   *                         ...
72   *                   /[region name]           &lt;---- All the region's information
73   *                   .regioninfo              &lt;---- Copy of the HRegionInfo
74   *                      /[column family name]
75   *                          /[hfile name]     &lt;--- name of the hfile in the real region
76   *                          ...
77   *                      ...
78   *                    ...
79   * </pre>
80   *
81   * Utility methods in this class are useful for getting the correct locations for different parts of
82   * the snapshot, as well as moving completed snapshots into place (see
83   * {@link #completeSnapshot}, and writing the
84   * {@link SnapshotDescription} to the working snapshot directory.
85   */
86  @InterfaceAudience.Private
87  public final class SnapshotDescriptionUtils {
88  
89    /**
90     * Filter that only accepts completed snapshot directories
91     */
92    public static class CompletedSnaphotDirectoriesFilter extends FSUtils.BlackListDirFilter {
93  
94      /**
95       * @param fs
96       */
97      public CompletedSnaphotDirectoriesFilter(FileSystem fs) {
98        super(fs, Collections.singletonList(SNAPSHOT_TMP_DIR_NAME));
99      }
100   }
101 
102   private static final Log LOG = LogFactory.getLog(SnapshotDescriptionUtils.class);
103   /**
104    * Version of the fs layout for a snapshot. Future snapshots may have different file layouts,
105    * which we may need to read in differently.
106    */
107   public static final int SNAPSHOT_LAYOUT_VERSION = SnapshotManifestV2.DESCRIPTOR_VERSION;
108 
109   // snapshot directory constants
110   /**
111    * The file contains the snapshot basic information and it is under the directory of a snapshot.
112    */
113   public static final String SNAPSHOTINFO_FILE = ".snapshotinfo";
114 
115   /** Temporary directory under the snapshot directory to store in-progress snapshots */
116   public static final String SNAPSHOT_TMP_DIR_NAME = ".tmp";
117 
118   /**
119    * The configuration property that determines the filepath of the snapshot
120    * base working directory
121    */
122   public static final String SNAPSHOT_WORKING_DIR = "hbase.snapshot.working.dir";
123 
124   // snapshot operation values
125   /** Default value if no start time is specified */
126   public static final long NO_SNAPSHOT_START_TIME_SPECIFIED = 0;
127 
128 
129   public static final String MASTER_SNAPSHOT_TIMEOUT_MILLIS = "hbase.snapshot.master.timeout.millis";
130 
131   /** By default, wait 300 seconds for a snapshot to complete */
132   public static final long DEFAULT_MAX_WAIT_TIME = 60000 * 5 ;
133 
134 
135   /**
136    * By default, check to see if the snapshot is complete (ms)
137    * @deprecated Use {@link #DEFAULT_MAX_WAIT_TIME} instead.
138    * */
139   @Deprecated
140   public static final int SNAPSHOT_TIMEOUT_MILLIS_DEFAULT = 60000 * 5;
141 
142   /**
143    * Conf key for # of ms elapsed before injecting a snapshot timeout error when waiting for
144    * completion.
145    * @deprecated Use {@link #MASTER_SNAPSHOT_TIMEOUT_MILLIS} instead.
146    */
147   @Deprecated
148   public static final String SNAPSHOT_TIMEOUT_MILLIS_KEY = "hbase.snapshot.master.timeoutMillis";
149 
150   private SnapshotDescriptionUtils() {
151     // private constructor for utility class
152   }
153 
154   /**
155    * @param conf {@link Configuration} from which to check for the timeout
156    * @param type type of snapshot being taken
157    * @param defaultMaxWaitTime Default amount of time to wait, if none is in the configuration
158    * @return the max amount of time the master should wait for a snapshot to complete
159    */
160   public static long getMaxMasterTimeout(Configuration conf, SnapshotDescription.Type type,
161       long defaultMaxWaitTime) {
162     String confKey;
163     switch (type) {
164     case DISABLED:
165     default:
166       confKey = MASTER_SNAPSHOT_TIMEOUT_MILLIS;
167     }
168     return Math.max(conf.getLong(confKey, defaultMaxWaitTime),
169         conf.getLong(SNAPSHOT_TIMEOUT_MILLIS_KEY, defaultMaxWaitTime));
170   }
171 
172   /**
173    * Get the snapshot root directory. All the snapshots are kept under this directory, i.e.
174    * ${hbase.rootdir}/.snapshot
175    * @param rootDir hbase root directory
176    * @return the base directory in which all snapshots are kept
177    */
178   public static Path getSnapshotRootDir(final Path rootDir) {
179     return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME);
180   }
181 
182   /**
183    * Get the directory for a specified snapshot. This directory is a sub-directory of snapshot root
184    * directory and all the data files for a snapshot are kept under this directory.
185    * @param snapshot snapshot being taken
186    * @param rootDir hbase root directory
187    * @return the final directory for the completed snapshot
188    */
189   public static Path getCompletedSnapshotDir(final SnapshotDescription snapshot, final Path rootDir) {
190     return getCompletedSnapshotDir(snapshot.getName(), rootDir);
191   }
192 
193   /**
194    * Get the directory for a completed snapshot. This directory is a sub-directory of snapshot root
195    * directory and all the data files for a snapshot are kept under this directory.
196    * @param snapshotName name of the snapshot being taken
197    * @param rootDir hbase root directory
198    * @return the final directory for the completed snapshot
199    */
200   public static Path getCompletedSnapshotDir(final String snapshotName, final Path rootDir) {
201     return getSpecifiedSnapshotDir(getSnapshotsDir(rootDir), snapshotName);
202   }
203 
204   /**
205    * Get the general working directory for snapshots - where they are built, where they are
206    * temporarily copied on export, etc.
207    * @param rootDir root directory of the HBase installation
208    * @param conf Configuration of the HBase instance
209    * @return Path to the snapshot tmp directory, relative to the passed root directory
210    */
211   public static Path getWorkingSnapshotDir(final Path rootDir, final Configuration conf) {
212     return new Path(conf.get(SNAPSHOT_WORKING_DIR,
213         getDefaultWorkingSnapshotDir(rootDir).toString()));
214   }
215 
216   /**
217    * Get the directory to build a snapshot, before it is finalized
218    * @param snapshot snapshot that will be built
219    * @param rootDir root directory of the hbase installation
220    * @param conf Configuration of the HBase instance
221    * @return {@link Path} where one can build a snapshot
222    */
223   public static Path getWorkingSnapshotDir(SnapshotDescription snapshot, final Path rootDir,
224       Configuration conf) {
225     return getWorkingSnapshotDir(snapshot.getName(), rootDir, conf);
226   }
227 
228   /**
229    * Get the directory to build a snapshot, before it is finalized
230    * @param snapshotName name of the snapshot
231    * @param rootDir root directory of the hbase installation
232    * @param conf Configuration of the HBase instance
233    * @return {@link Path} where one can build a snapshot
234    */
235   public static Path getWorkingSnapshotDir(String snapshotName, final Path rootDir,
236       Configuration conf) {
237     return getSpecifiedSnapshotDir(getWorkingSnapshotDir(rootDir, conf), snapshotName);
238   }
239 
240   /**
241    * Get the directory within the given filepath to store the snapshot instance
242    * @param snapshotsDir directory to store snapshot directory within
243    * @param snapshotName name of the snapshot to take
244    * @return the final directory for the snapshot in the given filepath
245    */
246   private static final Path getSpecifiedSnapshotDir(final Path snapshotsDir, String snapshotName) {
247     return new Path(snapshotsDir, snapshotName);
248   }
249 
250   /**
251    * @param rootDir hbase root directory
252    * @return the directory for all completed snapshots;
253    */
254   public static final Path getSnapshotsDir(Path rootDir) {
255     return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME);
256   }
257 
258   /**
259    * Determines if the given workingDir is a subdirectory of the given "root directory"
260    * @param workingDir a directory to check
261    * @param rootDir root directory of the HBase installation
262    * @return true if the given workingDir is a subdirectory of the given root directory,
263    *   false otherwise
264    */
265   public static boolean isSubDirectoryOf(final Path workingDir, final Path rootDir) {
266     return workingDir.toString().startsWith(rootDir.toString() + Path.SEPARATOR);
267   }
268 
269   /**
270    * Determines if the given workingDir is a subdirectory of the default working snapshot directory
271    * @param workingDir a directory to check
272    * @param conf configuration for the HBase cluster
273    * @return true if the given workingDir is a subdirectory of the default working directory for
274    *   snapshots, false otherwise
275    */
276   public static boolean isWithinDefaultWorkingDir(final Path workingDir, Configuration conf)
277       throws IOException {
278     Path defaultWorkingDir = getDefaultWorkingSnapshotDir(FSUtils.getRootDir(conf));
279     return workingDir.equals(defaultWorkingDir) || isSubDirectoryOf(workingDir, defaultWorkingDir);
280   }
281 
282   /**
283    * Get the default working directory for snapshots - where they are built, where they are
284    * temporarily copied on export, etc.
285    * @param rootDir root directory of the HBase installation
286    * @return Path to the default snapshot tmp directory, relative to the passed root directory
287    */
288   private static Path getDefaultWorkingSnapshotDir(final Path rootDir) {
289     return new Path(getSnapshotsDir(rootDir), SNAPSHOT_TMP_DIR_NAME);
290   }
291 
292   /**
293    * Convert the passed snapshot description into a 'full' snapshot description based on default
294    * parameters, if none have been supplied. This resolves any 'optional' parameters that aren't
295    * supplied to their default values.
296    * @param snapshot general snapshot descriptor
297    * @param conf Configuration to read configured snapshot defaults if snapshot is not complete
298    * @return a valid snapshot description
299    * @throws IllegalArgumentException if the {@link SnapshotDescription} is not a complete
300    *           {@link SnapshotDescription}.
301    */
302   public static SnapshotDescription validate(SnapshotDescription snapshot, Configuration conf)
303       throws IllegalArgumentException, IOException {
304     if (!snapshot.hasTable()) {
305       throw new IllegalArgumentException(
306         "Descriptor doesn't apply to a table, so we can't build it.");
307     }
308 
309     // set the creation time, if one hasn't been set
310     long time = snapshot.getCreationTime();
311     if (time == SnapshotDescriptionUtils.NO_SNAPSHOT_START_TIME_SPECIFIED) {
312       time = EnvironmentEdgeManager.currentTime();
313       LOG.debug("Creation time not specified, setting to:" + time + " (current time:"
314           + EnvironmentEdgeManager.currentTime() + ").");
315       SnapshotDescription.Builder builder = snapshot.toBuilder();
316       builder.setCreationTime(time);
317       snapshot = builder.build();
318     }
319 
320     // set the acl to snapshot if security feature is enabled.
321     if(isSecurityAvailable(conf)){
322       snapshot = writeAclToSnapshotDescription(snapshot, conf);
323     }
324 
325     return snapshot;
326   }
327 
328   /**
329    * Write the snapshot description into the working directory of a snapshot
330    * @param snapshot description of the snapshot being taken
331    * @param workingDir working directory of the snapshot
332    * @param fs {@link FileSystem} on which the snapshot should be taken
333    * @throws IOException if we can't reach the filesystem and the file cannot be cleaned up on
334    *           failure
335    */
336   public static void writeSnapshotInfo(SnapshotDescription snapshot, Path workingDir, FileSystem fs)
337       throws IOException {
338     FsPermission perms = FSUtils.getFilePermissions(fs, fs.getConf(),
339       HConstants.DATA_FILE_UMASK_KEY);
340     Path snapshotInfo = new Path(workingDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
341     try {
342       FSDataOutputStream out = FSUtils.create(fs, snapshotInfo, perms, true);
343       try {
344         snapshot.writeTo(out);
345       } finally {
346         out.close();
347       }
348     } catch (IOException e) {
349       // if we get an exception, try to remove the snapshot info
350       if (!fs.delete(snapshotInfo, false)) {
351         String msg = "Couldn't delete snapshot info file: " + snapshotInfo;
352         LOG.error(msg);
353         throw new IOException(msg);
354       }
355     }
356   }
357 
358   /**
359    * Read in the {@link SnapshotDescription} stored for the snapshot in the passed directory
360    * @param fs filesystem where the snapshot was taken
361    * @param snapshotDir directory where the snapshot was stored
362    * @return the stored snapshot description
363    * @throws CorruptedSnapshotException if the
364    * snapshot cannot be read
365    */
366   public static SnapshotDescription readSnapshotInfo(FileSystem fs, Path snapshotDir)
367       throws CorruptedSnapshotException {
368     Path snapshotInfo = new Path(snapshotDir, SNAPSHOTINFO_FILE);
369     try {
370       FSDataInputStream in = null;
371       try {
372         in = fs.open(snapshotInfo);
373         SnapshotDescription desc = SnapshotDescription.parseFrom(in);
374         return desc;
375       } finally {
376         if (in != null) in.close();
377       }
378     } catch (IOException e) {
379       throw new CorruptedSnapshotException("Couldn't read snapshot info from:" + snapshotInfo, e);
380     }
381   }
382 
383   /**
384    * Move the finished snapshot to its final, publicly visible directory - this marks the snapshot
385    * as 'complete'.
386    * @param snapshotDir The file path of the completed snapshots
387    * @param workingDir The file path of the in progress snapshots
388    * @param fs {@link FileSystem} The file system of the completed snapshots
389    * @param workingDirFs The file system of the in progress snapshots
390    * @throws org.apache.hadoop.hbase.snapshot.SnapshotCreationException if the
391    * snapshot could not be moved
392    * @throws IOException the filesystem could not be reached
393    */
394   public static void completeSnapshot(Path snapshotDir, Path workingDir, FileSystem fs,
395       FileSystem workingDirFs, Configuration conf) throws SnapshotCreationException, IOException {
396     LOG.debug("Snapshot is done, just moving the snapshot from " + workingDir + " to "
397         + snapshotDir);
398     if (!workingDirFs.exists(workingDir)) {
399       throw new IOException("Failed to moving nonexistent snapshot working directory "
400           + workingDir + " to " + snapshotDir);
401     }
402     // If the working and completed snapshot directory are on the same file system, attempt
403     // to rename the working snapshot directory to the completed location. If that fails,
404     // or the file systems differ, attempt to copy the directory over, throwing an exception
405     // if this fails
406     URI workingURI = workingDirFs.getUri();
407     URI rootURI = fs.getUri();
408 
409     if ((shouldSkipRenameSnapshotDirectories(workingURI, rootURI)
410         || !fs.rename(workingDir, snapshotDir))
411          && !FileUtil.copy(workingDirFs, workingDir, fs, snapshotDir, true, true, conf)) {
412       throw new SnapshotCreationException("Failed to copy working directory(" + workingDir
413           + ") to completed directory(" + snapshotDir + ").");
414     }
415   }
416 
417   @VisibleForTesting
418   static boolean shouldSkipRenameSnapshotDirectories(URI workingURI, URI rootURI) {
419     // check scheme, e.g. file, hdfs
420     if (workingURI.getScheme() == null &&
421         (rootURI.getScheme() != null && !rootURI.getScheme().equalsIgnoreCase("file"))) {
422       return true;
423     }
424     if (workingURI.getScheme() != null && !workingURI.getScheme().equals(rootURI.getScheme())) {
425       return true;
426     }
427 
428     // check Authority, e.g. localhost:port
429     if (workingURI.getAuthority() == null && rootURI.getAuthority() != null) {
430       return true;
431     }
432     if (workingURI.getAuthority() != null &&
433         !workingURI.getAuthority().equals(rootURI.getAuthority())) {
434       return true;
435     }
436 
437     // check UGI/userInfo
438     if (workingURI.getUserInfo() == null && rootURI.getUserInfo() != null) {
439       return true;
440     }
441     if (workingURI.getUserInfo() != null &&
442         !workingURI.getUserInfo().equals(rootURI.getUserInfo())) {
443       return true;
444     }
445 
446     return false;
447   }
448 
449 
450   /**
451    * Check if the user is this table snapshot's owner
452    * @param snapshot the table snapshot description
453    * @param user the user
454    * @return true if the user is the owner of the snapshot,
455    *         false otherwise or the snapshot owner field is not present.
456    */
457   public static boolean isSnapshotOwner(final SnapshotDescription snapshot, final User user) {
458     if (user == null) return false;
459     if (!snapshot.hasOwner()) return false;
460     return snapshot.getOwner().equals(user.getShortName());
461   }
462 
463   public static boolean isSecurityAvailable(Configuration conf) throws IOException {
464     Connection conn = ConnectionFactory.createConnection(conf);
465     try {
466       Admin admin = conn.getAdmin();
467       try {
468         return admin.tableExists(AccessControlLists.ACL_TABLE_NAME);
469       } finally {
470         admin.close();
471       }
472     } finally {
473       conn.close();
474     }
475   }
476 
477   private static SnapshotDescription writeAclToSnapshotDescription(
478       final SnapshotDescription snapshot, final Configuration conf) throws IOException {
479     ListMultimap<String, TablePermission> perms =
480         User.runAsLoginUser(new PrivilegedExceptionAction<ListMultimap<String, TablePermission>>() {
481           @Override
482           public ListMultimap<String, TablePermission> run() throws Exception {
483             return AccessControlLists.getTablePermissions(conf,
484               TableName.valueOf(snapshot.getTable()));
485           }
486         });
487     return snapshot.toBuilder().setUsersAndPermissions(ProtobufUtil.toUserTablePermissions(perms))
488         .build();
489   }
490 }