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