001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.snapshot;
019
020import java.io.IOException;
021import java.net.URI;
022import java.security.PrivilegedExceptionAction;
023import java.util.Collections;
024import java.util.concurrent.TimeUnit;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.FSDataInputStream;
027import org.apache.hadoop.fs.FSDataOutputStream;
028import org.apache.hadoop.fs.FileSystem;
029import org.apache.hadoop.fs.FileUtil;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.fs.permission.FsPermission;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.client.Admin;
035import org.apache.hadoop.hbase.client.Connection;
036import org.apache.hadoop.hbase.client.ConnectionFactory;
037import org.apache.hadoop.hbase.security.User;
038import org.apache.hadoop.hbase.security.access.PermissionStorage;
039import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
040import org.apache.hadoop.hbase.security.access.UserPermission;
041import org.apache.hadoop.hbase.util.CommonFSUtils;
042import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
043import org.apache.hadoop.hbase.util.FSUtils;
044import org.apache.yetus.audience.InterfaceAudience;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
049
050import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
052
053/**
054 * Utility class to help manage {@link SnapshotDescription SnapshotDesriptions}.
055 * <p>
056 * Snapshots are laid out on disk like this:
057 *
058 * <pre>
059 * /hbase/.snapshots
060 *          /.tmp                &lt;---- working directory
061 *          /[snapshot name]     &lt;----- completed snapshot
062 * </pre>
063 *
064 * A completed snapshot named 'completed' then looks like (multiple regions, servers, files, etc.
065 * signified by '...' on the same directory depth).
066 *
067 * <pre>
068 * /hbase/.snapshots/completed
069 *                   .snapshotinfo          &lt;--- Description of the snapshot
070 *                   .tableinfo             &lt;--- Copy of the tableinfo
071 *                    /.logs
072 *                        /[server_name]
073 *                            /... [log files]
074 *                         ...
075 *                   /[region name]           &lt;---- All the region's information
076 *                   .regioninfo              &lt;---- Copy of the HRegionInfo
077 *                      /[column family name]
078 *                          /[hfile name]     &lt;--- name of the hfile in the real region
079 *                          ...
080 *                      ...
081 *                    ...
082 * </pre>
083 *
084 * Utility methods in this class are useful for getting the correct locations for different parts of
085 * the snapshot, as well as moving completed snapshots into place (see
086 * {@link #completeSnapshot}, and writing the
087 * {@link SnapshotDescription} to the working snapshot directory.
088 */
089@InterfaceAudience.Private
090public final class SnapshotDescriptionUtils {
091
092  /**
093   * Filter that only accepts completed snapshot directories
094   */
095  public static class CompletedSnaphotDirectoriesFilter extends FSUtils.BlackListDirFilter {
096
097    /**
098     * @param fs
099     */
100    public CompletedSnaphotDirectoriesFilter(FileSystem fs) {
101      super(fs, Collections.singletonList(SNAPSHOT_TMP_DIR_NAME));
102    }
103  }
104
105  private static final Logger LOG = LoggerFactory.getLogger(SnapshotDescriptionUtils.class);
106  /**
107   * Version of the fs layout for a snapshot. Future snapshots may have different file layouts,
108   * which we may need to read in differently.
109   */
110  public static final int SNAPSHOT_LAYOUT_VERSION = SnapshotManifestV2.DESCRIPTOR_VERSION;
111
112  // snapshot directory constants
113  /**
114   * The file contains the snapshot basic information and it is under the directory of a snapshot.
115   */
116  public static final String SNAPSHOTINFO_FILE = ".snapshotinfo";
117
118  /** Temporary directory under the snapshot directory to store in-progress snapshots */
119  public static final String SNAPSHOT_TMP_DIR_NAME = ".tmp";
120
121  /**
122   * The configuration property that determines the filepath of the snapshot
123   * base working directory
124   */
125  public static final String SNAPSHOT_WORKING_DIR = "hbase.snapshot.working.dir";
126
127  // snapshot operation values
128  /** Default value if no start time is specified */
129  public static final long NO_SNAPSHOT_START_TIME_SPECIFIED = 0;
130
131  // Default value if no ttl is specified for Snapshot
132  private static final long NO_SNAPSHOT_TTL_SPECIFIED = 0;
133
134  public static final String MASTER_SNAPSHOT_TIMEOUT_MILLIS = "hbase.snapshot.master.timeout.millis";
135
136  /** By default, wait 300 seconds for a snapshot to complete */
137  public static final long DEFAULT_MAX_WAIT_TIME = 60000 * 5 ;
138
139  private SnapshotDescriptionUtils() {
140    // private constructor for utility class
141  }
142
143  /**
144   * @param conf {@link Configuration} from which to check for the timeout
145   * @param type type of snapshot being taken
146   * @param defaultMaxWaitTime Default amount of time to wait, if none is in the configuration
147   * @return the max amount of time the master should wait for a snapshot to complete
148   */
149  public static long getMaxMasterTimeout(Configuration conf, SnapshotDescription.Type type,
150      long defaultMaxWaitTime) {
151    String confKey;
152    switch (type) {
153    case DISABLED:
154    default:
155      confKey = MASTER_SNAPSHOT_TIMEOUT_MILLIS;
156    }
157    return Math.max(conf.getLong(confKey, defaultMaxWaitTime),
158        conf.getLong(MASTER_SNAPSHOT_TIMEOUT_MILLIS, defaultMaxWaitTime));
159  }
160
161  /**
162   * Get the snapshot root directory. All the snapshots are kept under this directory, i.e.
163   * ${hbase.rootdir}/.snapshot
164   * @param rootDir hbase root directory
165   * @return the base directory in which all snapshots are kept
166   */
167  public static Path getSnapshotRootDir(final Path rootDir) {
168    return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME);
169  }
170
171  /**
172   * Get the directory for a specified snapshot. This directory is a sub-directory of snapshot root
173   * directory and all the data files for a snapshot are kept under this directory.
174   * @param snapshot snapshot being taken
175   * @param rootDir hbase root directory
176   * @return the final directory for the completed snapshot
177   */
178  public static Path getCompletedSnapshotDir(final SnapshotDescription snapshot, final Path rootDir) {
179    return getCompletedSnapshotDir(snapshot.getName(), rootDir);
180  }
181
182  /**
183   * Get the directory for a completed 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 snapshotName name of the 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 String snapshotName, final Path rootDir) {
190    return getSpecifiedSnapshotDir(getSnapshotsDir(rootDir), snapshotName);
191  }
192
193  /**
194   * Get the general working directory for snapshots - where they are built, where they are
195   * temporarily copied on export, etc.
196   * @param rootDir root directory of the HBase installation
197   * @param conf Configuration of the HBase instance
198   * @return Path to the snapshot tmp directory, relative to the passed root directory
199   */
200  public static Path getWorkingSnapshotDir(final Path rootDir, final Configuration conf) {
201    return new Path(conf.get(SNAPSHOT_WORKING_DIR,
202        getDefaultWorkingSnapshotDir(rootDir).toString()));
203  }
204
205  /**
206   * Get the directory to build a snapshot, before it is finalized
207   * @param snapshot snapshot that will be built
208   * @param rootDir root directory of the hbase installation
209   * @param conf Configuration of the HBase instance
210   * @return {@link Path} where one can build a snapshot
211   */
212  public static Path getWorkingSnapshotDir(SnapshotDescription snapshot, final Path rootDir,
213      Configuration conf) {
214    return getWorkingSnapshotDir(snapshot.getName(), rootDir, conf);
215  }
216
217  /**
218   * Get the directory to build a snapshot, before it is finalized
219   * @param snapshotName name of the snapshot
220   * @param rootDir root directory of the hbase installation
221   * @param conf Configuration of the HBase instance
222   * @return {@link Path} where one can build a snapshot
223   */
224  public static Path getWorkingSnapshotDir(String snapshotName, final Path rootDir,
225      Configuration conf) {
226    return getSpecifiedSnapshotDir(getWorkingSnapshotDir(rootDir, conf), snapshotName);
227  }
228
229  /**
230   * Get the directory within the given filepath to store the snapshot instance
231   * @param snapshotsDir directory to store snapshot directory within
232   * @param snapshotName name of the snapshot to take
233   * @return the final directory for the snapshot in the given filepath
234   */
235  private static final Path getSpecifiedSnapshotDir(final Path snapshotsDir, String snapshotName) {
236    return new Path(snapshotsDir, snapshotName);
237  }
238
239  /**
240   * @param rootDir hbase root directory
241   * @return the directory for all completed snapshots;
242   */
243  public static final Path getSnapshotsDir(Path rootDir) {
244    return new Path(rootDir, HConstants.SNAPSHOT_DIR_NAME);
245  }
246
247  /**
248   * Determines if the given workingDir is a subdirectory of the given "root directory"
249   * @param workingDir a directory to check
250   * @param rootDir root directory of the HBase installation
251   * @return true if the given workingDir is a subdirectory of the given root directory,
252   *   false otherwise
253   */
254  public static boolean isSubDirectoryOf(final Path workingDir, final Path rootDir) {
255    return workingDir.toString().startsWith(rootDir.toString() + Path.SEPARATOR);
256  }
257
258  /**
259   * Determines if the given workingDir is a subdirectory of the default working snapshot directory
260   * @param workingDir a directory to check
261   * @param conf configuration for the HBase cluster
262   * @return true if the given workingDir is a subdirectory of the default working directory for
263   *   snapshots, false otherwise
264   * @throws IOException if we can't get the root dir
265   */
266  public static boolean isWithinDefaultWorkingDir(final Path workingDir, Configuration conf)
267    throws IOException {
268    Path defaultWorkingDir = getDefaultWorkingSnapshotDir(CommonFSUtils.getRootDir(conf));
269    return workingDir.equals(defaultWorkingDir) || isSubDirectoryOf(workingDir, defaultWorkingDir);
270  }
271
272  /**
273   * Get the default working directory for snapshots - where they are built, where they are
274   * temporarily copied on export, etc.
275   * @param rootDir root directory of the HBase installation
276   * @return Path to the default snapshot tmp directory, relative to the passed root directory
277   */
278  private static Path getDefaultWorkingSnapshotDir(final Path rootDir) {
279    return new Path(getSnapshotsDir(rootDir), SNAPSHOT_TMP_DIR_NAME);
280  }
281
282  /**
283   * Convert the passed snapshot description into a 'full' snapshot description based on default
284   * parameters, if none have been supplied. This resolves any 'optional' parameters that aren't
285   * supplied to their default values.
286   * @param snapshot general snapshot descriptor
287   * @param conf Configuration to read configured snapshot defaults if snapshot is not complete
288   * @return a valid snapshot description
289   * @throws IllegalArgumentException if the {@link SnapshotDescription} is not a complete
290   *           {@link SnapshotDescription}.
291   */
292  public static SnapshotDescription validate(SnapshotDescription snapshot, Configuration conf)
293      throws IllegalArgumentException, IOException {
294    if (!snapshot.hasTable()) {
295      throw new IllegalArgumentException(
296          "Descriptor doesn't apply to a table, so we can't build it.");
297    }
298
299    // set the creation time, if one hasn't been set
300    long time = snapshot.getCreationTime();
301    if (time == SnapshotDescriptionUtils.NO_SNAPSHOT_START_TIME_SPECIFIED) {
302      time = EnvironmentEdgeManager.currentTime();
303      LOG.debug("Creation time not specified, setting to:" + time + " (current time:"
304          + EnvironmentEdgeManager.currentTime() + ").");
305      SnapshotDescription.Builder builder = snapshot.toBuilder();
306      builder.setCreationTime(time);
307      snapshot = builder.build();
308    }
309
310    long ttl = snapshot.getTtl();
311    // set default ttl(sec) if it is not set already or the value is out of the range
312    if (ttl == SnapshotDescriptionUtils.NO_SNAPSHOT_TTL_SPECIFIED ||
313        ttl > TimeUnit.MILLISECONDS.toSeconds(Long.MAX_VALUE)) {
314      final long defaultSnapshotTtl = conf.getLong(HConstants.DEFAULT_SNAPSHOT_TTL_CONFIG_KEY,
315          HConstants.DEFAULT_SNAPSHOT_TTL);
316      if (LOG.isDebugEnabled()) {
317        LOG.debug("Snapshot current TTL value: {} resetting it to default value: {}", ttl,
318            defaultSnapshotTtl);
319      }
320      ttl = defaultSnapshotTtl;
321    }
322    SnapshotDescription.Builder builder = snapshot.toBuilder();
323    builder.setTtl(ttl);
324    snapshot = builder.build();
325
326    // set the acl to snapshot if security feature is enabled.
327    if (isSecurityAvailable(conf)) {
328      snapshot = writeAclToSnapshotDescription(snapshot, conf);
329    }
330    return snapshot;
331  }
332
333  /**
334   * Write the snapshot description into the working directory of a snapshot
335   * @param snapshot description of the snapshot being taken
336   * @param workingDir working directory of the snapshot
337   * @param fs {@link FileSystem} on which the snapshot should be taken
338   * @throws IOException if we can't reach the filesystem and the file cannot be cleaned up on
339   *           failure
340   */
341  public static void writeSnapshotInfo(SnapshotDescription snapshot, Path workingDir, FileSystem fs)
342      throws IOException {
343    FsPermission perms = CommonFSUtils.getFilePermissions(fs, fs.getConf(),
344      HConstants.DATA_FILE_UMASK_KEY);
345    Path snapshotInfo = new Path(workingDir, SnapshotDescriptionUtils.SNAPSHOTINFO_FILE);
346    try (FSDataOutputStream out = CommonFSUtils.create(fs, snapshotInfo, perms, true)){
347      snapshot.writeTo(out);
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 snapshot cannot be read
364   */
365  public static SnapshotDescription readSnapshotInfo(FileSystem fs, Path snapshotDir)
366      throws CorruptedSnapshotException {
367    Path snapshotInfo = new Path(snapshotDir, SNAPSHOTINFO_FILE);
368    try (FSDataInputStream in = fs.open(snapshotInfo)){
369      return SnapshotDescription.parseFrom(in);
370    } catch (IOException e) {
371      throw new CorruptedSnapshotException("Couldn't read snapshot info from:" + snapshotInfo, e);
372    }
373  }
374
375  /**
376   * Commits the snapshot process by moving the working snapshot
377   * to the finalized filepath
378   *
379   * @param snapshotDir The file path of the completed snapshots
380   * @param workingDir  The file path of the in progress snapshots
381   * @param fs The file system of the completed snapshots
382   * @param workingDirFs The file system of the in progress snapshots
383   * @param conf Configuration
384   *
385   * @throws SnapshotCreationException if the snapshot could not be moved
386   * @throws IOException the filesystem could not be reached
387   */
388  public static void completeSnapshot(Path snapshotDir, Path workingDir, FileSystem fs,
389    FileSystem workingDirFs, final Configuration conf)
390    throws SnapshotCreationException, IOException {
391    LOG.debug("Sentinel is done, just moving the snapshot from " + workingDir + " to "
392      + snapshotDir);
393    // If the working and completed snapshot directory are on the same file system, attempt
394    // to rename the working snapshot directory to the completed location. If that fails,
395    // or the file systems differ, attempt to copy the directory over, throwing an exception
396    // if this fails
397    URI workingURI = workingDirFs.getUri();
398    URI rootURI = fs.getUri();
399    if ((!workingURI.getScheme().equals(rootURI.getScheme()) ||
400      workingURI.getAuthority() == null ||
401      !workingURI.getAuthority().equals(rootURI.getAuthority()) ||
402      workingURI.getUserInfo() == null ||
403      !workingURI.getUserInfo().equals(rootURI.getUserInfo()) ||
404      !fs.rename(workingDir, snapshotDir)) && !FileUtil.copy(workingDirFs, workingDir, fs,
405      snapshotDir, true, true, conf)) {
406      throw new SnapshotCreationException("Failed to copy working directory(" + workingDir
407        + ") to completed directory(" + snapshotDir + ").");
408    }
409  }
410
411  /**
412   * Check if the user is this table snapshot's owner
413   * @param snapshot the table snapshot description
414   * @param user the user
415   * @return true if the user is the owner of the snapshot,
416   *         false otherwise or the snapshot owner field is not present.
417   */
418  public static boolean isSnapshotOwner(org.apache.hadoop.hbase.client.SnapshotDescription snapshot,
419      User user) {
420    if (user == null) return false;
421    return user.getShortName().equals(snapshot.getOwner());
422  }
423
424  public static boolean isSecurityAvailable(Configuration conf) throws IOException {
425    try (Connection conn = ConnectionFactory.createConnection(conf); Admin admin = conn.getAdmin()) {
426      return admin.tableExists(PermissionStorage.ACL_TABLE_NAME);
427    }
428  }
429
430  private static SnapshotDescription writeAclToSnapshotDescription(SnapshotDescription snapshot,
431      Configuration conf) throws IOException {
432    ListMultimap<String, UserPermission> perms =
433        User.runAsLoginUser(new PrivilegedExceptionAction<ListMultimap<String, UserPermission>>() {
434          @Override
435          public ListMultimap<String, UserPermission> run() throws Exception {
436            return PermissionStorage.getTablePermissions(conf,
437              TableName.valueOf(snapshot.getTable()));
438          }
439        });
440    return snapshot.toBuilder()
441        .setUsersAndPermissions(ShadedAccessControlUtil.toUserTablePermissions(perms)).build();
442  }
443}